> ## Documentation Index
> Fetch the complete documentation index at: https://commet.co/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate with Remix

> Add billing and payments to your Remix application with the Commet SDK, using actions and loaders for subscriptions and usage.

Install the Commet Skill so your coding agent can integrate the current SDK and verify its work against the live API contract.

```bash theme={null}
npx skills add commet-labs/skills --skill commet
```

**1. Install**

> [tab] pnpm
>
> ```bash theme={null}
> pnpm add @commet/node @remix-run/node
> ```

> [tab] npm
>
> ```bash theme={null}
> npm install @commet/node @remix-run/node
> ```

> [tab] yarn
>
> ```bash theme={null}
> yarn add @commet/node @remix-run/node
> ```

> [tab] bun
>
> ```bash theme={null}
> bun add @commet/node @remix-run/node
> ```

**2. Configure**

```bash title=".env" theme={null}
COMMET_API_KEY=ck_sandbox_xxx
```

The `.server.ts` suffix ensures this module is never bundled into client code.

```typescript title="app/lib/commet.server.ts" theme={null}
import { Commet } from '@commet/node'

export const commet = new Commet({
  apiKey: process.env.COMMET_API_KEY!,
})
```

**3. Subscribe**

`customers.create` is idempotent — if a customer with the same `id` already exists, it returns the existing record.

```typescript title="app/routes/billing.subscribe.ts" theme={null}
import { json, redirect, type ActionFunctionArgs } from '@remix-run/node'
import { commet } from '~/lib/commet.server'

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData()
  const customerId = String(formData.get('customerId'))
  const email = String(formData.get('email'))

  await commet.customers.create({ email, id: customerId })

  const subscription = await commet.subscriptions.create({
    customerId,
    planCode: 'pro',
  })

  const checkoutUrl = subscription.checkoutUrl
  if (checkoutUrl) {
    return redirect(checkoutUrl)
  }

  return json({ subscribed: true })
}
```

**4. Check Access**

```typescript title="app/routes/billing.status.ts" theme={null}
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { commet } from '~/lib/commet.server'

export async function loader({ request }: LoaderFunctionArgs) {
  const url = new URL(request.url)
  const customerId = url.searchParams.get('customerId')!

  const subscription = await commet.subscriptions.getActive({ customerId })

  if (!subscription) {
    return json({ error: 'no_active_subscription' }, { status: 404 })
  }

  const feature = await commet.featureAccess.get({
    code: 'api_calls',
    customerId,
  })

  return json({
    status: subscription.status,
    allowed: feature.allowed,
  })
}
```

**5. Track Usage**

```typescript title="app/routes/billing.usage.ts" theme={null}
import { json, type ActionFunctionArgs } from '@remix-run/node'
import { commet } from '~/lib/commet.server'

export async function action({ request }: ActionFunctionArgs) {
  const { customerId } = await request.json()

  await commet.usage.track({
    customerId,
    featureCode: 'api_calls',
    value: 1,
  })

  return json({ tracked: true })
}
```

Usage is aggregated and billed at end of period.

**6. Customer Portal**

```typescript title="app/routes/billing.portal.ts" theme={null}
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
import { commet } from '~/lib/commet.server'

export async function loader({ request }: LoaderFunctionArgs) {
  const customerId = 'user_123'

  const portal = await commet.portal.getUrl({ customerId })

  return redirect(portal.portalUrl)
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.