---
title: "Offer discounts"
description: "Mint, list and remove discounts from server code, let customers type codes at checkout, pass a campaign's code, or apply a discount your server chooses."
canonical_url: "https://nuxt-backend.dev/docs/billing/subscriptions/discounts"
---
# Offer discounts

> Mint, list and remove discounts from server code, let customers type codes at checkout, pass a campaign's code, or apply a discount your server chooses.

Discounts live in the billing provider. `billing.discounts` manages them from your server: minting one is privileged, since a public function that creates discounts would let anyone make a 100%-off code, so the three methods are not registered functions and nothing in the browser can call them.

## Mint a discount

The scaffolded `backend/billing.ts` ships one, as an `internalAction`:

```ts [backend/billing.ts]
export const createDiscount = internalAction({
  args: { name: v.string(), percent: v.number(), code: v.optional(v.string()) },
  handler: async (ctx, { name, percent, code }) => {
    const basisPoints = Math.round(Math.min(Math.max(percent, 0), 100) * 100)
    const discount: DiscountInput = { type: 'percentage', name, code, duration: 'once', basisPoints }
    return billing.discounts.create(discount)
  },
})
```

Run it from the command line, or call it from your own server code:

```bash
npx convex run billing:createDiscount '{"name":"Launch","percent":20,"code":"LAUNCH20"}'
```

`billing.discounts.create(discount)` takes the provider's full shape (`DiscountInput`: percentage or fixed amount, duration, redemption limits, product restrictions, dates) and resolves with `{ id, code }`. For an admin screen, declare the same action with the `admin.action` builder from `backend/functions.ts` instead of `internalAction`: see [Protect backend functions](https://nuxt-backend.dev/docs/auth/access/functions).

## List and remove

```ts
const page = await billing.discounts.list({ query: 'LAUNCH', limit: 20 })   // { items, pagination, nextCursor? }
await billing.discounts.remove(discountId)   // redemptions already applied stay applied
```

## Codes at checkout

Customers type a code into the provider's hosted checkout. `allowDiscountCodes` is on by default; turn it off for a checkout that must not take one:

```ts
await useBilling().checkout(products.value!.pro!.id, { allowDiscountCodes: false })
```

A campaign link that carries a code passes it as `discountCode`, and the server applies it before the customer sees the checkout: see [Campaign links](https://nuxt-backend.dev/docs/billing/subscriptions/checkout#campaign-links).

## A discount the server chooses

A discount without a code is applied by its id, and only server code may name one: an id can name any discount, the 100% one minted for a single claim included. Pass it to `billing.checkout` from your own action, after deciding the caller qualifies; a client that sends `discountId` to `generateCheckoutLink` is refused as `invalid_input`:

```ts [backend/billing.ts]
export const founderCheckout = action({
  args: { productId: v.string(), origin: v.string(), successUrl: v.string() },
  handler: async (ctx, { productId, ...urls }) => {
    if (!(await isFounder(ctx))) throw backendError('forbidden', { reason: 'permission' })
    return billing.checkout(ctx, { productIds: [productId], ...urls, discountId: FOUNDER_DISCOUNT_ID })
  },
})
```

The provider's API has no code-to-id lookup, so resolve a code once with `billing.discounts.list()` and keep the id.

<page-keys>

- Try it live in the playground: [/playground/platform/billing](https://nuxt-backend.dev/playground/platform/billing)
- API reference: [/docs/reference/api/use-billing](https://nuxt-backend.dev/docs/reference/api/use-billing)
- Source: [https://github.com/qruto/nuxt-backend/blob/main/src/runtime/vue/composables/use-billing.ts](https://github.com/qruto/nuxt-backend/blob/main/src/runtime/vue/composables/use-billing.ts)

</page-keys>


## Sitemap

See the full [sitemap](https://nuxt-backend.dev/sitemap.md) for all pages.
