---
title: "Composables"
description: "Full signatures for every composable nuxt-backend registers. The core data composables (useQuery, useMutation, …) are nuxt-convex-module's."
canonical_url: "https://nuxt-backend.dev/docs/reference/nuxt/composables"
---
# Composables

> Full signatures for every composable nuxt-backend registers. The core data composables (useQuery, useMutation, …) are nuxt-convex-module's.

These are auto-imported in app code — no `import` needed. [Composable conventions](https://nuxt-backend.dev/docs/reference/nuxt/conventions) gives the shape of the data composables (`useSearch`, `useAggregate`, `useWorkflowStatus`, `useEmailStatus`, `useSandboxInbox`, `useSessions`): `isLoading`, `isPending`, and `error` as `BackendError | null` (`{ code, data, message }`). Generic helpers in the signatures (`FunctionReference`, `FunctionArgs`, `FunctionReturnType`, `OptionalRestArgs`, `ConnectionState`, `Value`) come from `convex`; the paginated result types are re-exported from `convex/react` for parity. Wherever a signature says `FunctionReference<…>`, Convex's newer `FunctionReference_future<…>` (convex 1.46+) is accepted too.

## Client access

```ts
function useBackendClient(): ConvexVueClient   // the imperative client; see Backend client
```

The base module's brand-named spellings of these composables (`useConvex`, the `useConvex*` aliases, `provideConvexAuth`, `useBetterAuth`) are not auto-imported in an app that uses nuxt-backend. The names on this page are the whole set.

## Queries

```ts
// Returns undefined while loading; throws query errors.
function useQuery<Q extends FunctionReference<'query'>>(
  query: Q,
  ...args: OptionalRestArgsOrSkip<Q>   // reactive args; pass 'skip' to pause
): ShallowRef<FunctionReturnType<Q> | undefined>

// Experimental object form — errors returned in result unless throwOnError.
function useQuery_experimental<Q extends FunctionReference<'query'>, ThrowOnError extends boolean = false>(
  options: { query: Q, args: MaybeRefOrGetter<FunctionArgs<Q> | 'skip'>, throwOnError?: ThrowOnError },
): ShallowRef<UseQueryResult<FunctionReturnType<Q>, ThrowOnError>>
// → { status: 'pending' | 'success' | 'error', data?, error? }

// Dynamic set of queries.
function useQueries(queries: MaybeRefOrGetter<RequestForQueries>): ShallowRef<QueriesResults>
// RequestForQueries: Record<string, { query: FunctionReference<'query'>, args: Record<string, Value> }>
// QueriesResults:    Record<string, Value | undefined | Error>
```

## Mutations & actions

```ts
function useMutation<M extends FunctionReference<'mutation'>>(mutation: M): VueMutation<M>
interface VueMutation<M> {
  (...args: OptionalRestArgs<M>): Promise<FunctionReturnType<M>>
  withOptimisticUpdate(update: OptimisticUpdate<FunctionArgs<M>>): VueMutation<M>
}

function useAction<A extends FunctionReference<'action'>>(action: A): VueAction<A>
interface VueAction<A> {
  (...args: OptionalRestArgs<A>): Promise<FunctionReturnType<A>>
}
```

## Pagination

```ts
// Positional form, throws on error.
function usePaginatedQuery<Q extends PaginatedQueryReference>(
  query: Q,
  args: MaybeRefOrGetter<PaginatedQueryArgs<Q> | 'skip'>,
  options: { initialNumItems: number },
): ShallowRef<UsePaginatedQueryReturnType<Q>>
// → { results, status: 'LoadingFirstPage' | 'CanLoadMore' | 'LoadingMore' | 'Exhausted', isLoading, loadMore }

// Experimental — overloaded: positional (throws) OR object form (errors in result).
function usePaginatedQuery_experimental<Q extends PaginatedQueryReference>(
  query: Q, args: MaybeRefOrGetter<PaginatedQueryArgs<Q> | 'skip'>, options: { initialNumItems: number },
): ShallowRef<UsePaginatedQueryReturnType<Q>>
function usePaginatedQuery_experimental<Q extends PaginatedQueryReference, ThrowOnError extends boolean = false>(
  options: MaybeRefOrGetter<UsePaginatedQueryOptions<Q, ThrowOnError>>,
): ShallowRef<UsePaginatedQueryObjectReturnType<Q, ThrowOnError>>
// object form → { data, status: 'pending' | 'success' | 'error', error, canLoadMore, isLoading, loadMore }
```

## SSR hydration

```ts
function usePreloadedQuery<Q extends FunctionReference<'query'>>(preloaded: Preloaded<Q>): ShallowRef<FunctionReturnType<Q>>
function usePreloadedAuthQuery<Q extends FunctionReference<'query'>>(preloaded: Preloaded<Q>): ShallowRef<FunctionReturnType<Q> | null>
```

## Connection state

```ts
function useConnectionState(): ShallowRef<ConnectionState>
```

## File storage

```ts
function useUpload(generateUploadUrl: GenerateUploadUrl, options?: UseUploadOptions): VueUpload
interface VueUpload {
  upload: (file: Blob) => Promise<StorageId | null>   // null on failure; see `error`
  isUploading: Ref<boolean>; progress: Ref<number>    // progress 0–1
  error: Ref<Error | null>; storageId: Ref<StorageId | null>
  cancel: () => void; reset: () => void
}

function useUploadQueue(generateUploadUrl: GenerateUploadUrl, options?: UseUploadQueueOptions): VueUploadQueue
// → { items, enqueue, isUploading, progress, pendingCount, activeCount, cancel, remove, clear }

function useStorageUrl(getUrl: GetStorageUrl, storageId: MaybeRefOrGetter<StorageId | string | null | undefined>): ShallowRef<string | null | undefined>

function uploadFile(options: { url: string, file: Blob, onProgress?: (p: number) => void, signal?: AbortSignal }): Promise<StorageId>
```

## Backend-component composables

The billing and email composables bind to the `api.billing` / `api.email`
namespaces the module provides from your scaffolded `backend/` files; every
one of them takes an `api` option to point at a different namespace.

```ts
// Debounced reactive full-text search over a `defineSearch` query (or any
// query taking `{ query: string }`); pauses while the term is blank.
function useSearch<Q extends FunctionReference<'query'>>(
  query: Q,
  options: {
    term: MaybeRefOrGetter<string | null | undefined>        // blank or whitespace-only pauses it
    debounce?: MaybeRefOrGetter<number | undefined>          // ms, default 200
    args?: MaybeRefOrGetter<Omit<FunctionArgs<Q>, 'query'>>  // merged with { query } — a limit, filter fields
  },
): {
  results: ComputedRef<FunctionReturnType<Q>>   // [] while loading, while the term is blank, after a failed read
  isLoading: ComputedRef<boolean>
  error: ComputedRef<BackendError | null>
  term: ComputedRef<string>                     // the debounced term driving the query
}

// Live aggregate (a count or a sum). Takes the query's args as useQuery does, 'skip' included.
function useAggregate<Q extends FunctionReference<'query'>>(query: Q, ...args: OptionalRestArgsOrSkip<Q>): {
  data: ComputedRef<number | undefined>   // undefined until the first read; 0 for a query that answers no number
  isLoading: ComputedRef<boolean>
  error: ComputedRef<BackendError | null>
}

// Billing. `gift(pack, …)` opens the purchaser's own checkout of a credit pack,
// by its catalog key, for someone else's email to claim. Every
// lifecycle action takes an optional `subscriptionId` — needed only once the
// backend runs with `multipleSubscriptions` and one account holds several.
function useBilling(options?: { api? }): {
  products, subscription, subscriptions, isSubscribed, isFree, isLoading,
  // Lifecycle state, read off the synced subscription row.
  status, cancelAtPeriodEnd, trialEnd, isTrialing, isPaused,
  // Reserved: the synced subscription table has no columns for these yet, so
  // they read `null` until the provider component adds them.
  pausedAt, resumesAt, pendingUpdate,
  checkout,    // (productIds, { discountCode?, utm?, metadata?, successUrl?, … }): the trial is the plan's own
  gift, portal,
  changePlan,  // (productId, { proration?: 'invoice' | 'prorate', subscriptionId? })
  cancel,      // ({ atPeriodEnd?, reason?, comment?, subscriptionId? })
  uncancel, pause, resume,
}

// Feature gating off the webhook-synced entitlement cache.
function useFeatures(options?: { api? }): {
  plans,      // ComputedRef<string[] | undefined> — active product ids
  benefits,   // ComputedRef<EntitlementBenefit[] | undefined> — granted benefits
  isLoading,  // true until the cache has loaded — gate on it to avoid a first-paint flicker
  has: (feature: string) => boolean,       // matched by benefitId, grant id, type, or any metadata value
  hasPlan: (productId: string) => boolean,
}

// Prepaid credits: the balance cache, the cycle it belongs to, and top-ups.
// `cycle` is `null` for credits bought as a one-time pack (they have no
// billing period); `expiresAtCycleEnd` is only `true` when the granting
// benefit is known not to roll over.
function useCredits(meterId?: MaybeRefOrGetter<string>, options?: { api? }): {
  balance, credited, consumed, overage, cycle, expiresAtCycleEnd,
  meterId, isLoading, topUp, gift, refresh,
}

// Past charges, read live from the provider (no local order table). Paged by
// provider page number. `invoice(orderId)` fetches the invoice link and opens
// it, resolving with the URL — `null` while the provider has no invoice PDF
// for that order yet; pass `{ open: false }` to get the URL without opening it.
function useOrders(options?: { limit?, immediate?, api? }): {
  orders, page, total, pageCount, hasMore, hasPrevious, isLoading, error,
  refresh, next, previous, goTo,
  invoice: (orderId: string, options?: { redirect?: boolean, open?: boolean }) => Promise<string | null>,
}

// Metered consumption for one credit meter, read live from the provider's
// events API — the events your server ingested when it spent credits.
function useUsage(options?: { meter?, limit?, immediate?, api? }): {
  events, page, total, pageCount, units, hasMore, hasPrevious,
  isLoading, error, refresh, next, previous, goTo,
}

// Gifts addressed to the signed-in user. Nothing is claimed until they ask.
function useGifts(options?: { api? }): {
  received,    // the paid and claimed gifts addressed to the user's email, undefined while loading
  unclaimed,   // the paid-but-unclaimed subset
  isLoading,   // true until the list has loaded (always false when signed out)
  isClaiming,  // true while a claim is in flight
  // One gift into the user's own billing account: true when the server
  // confirmed it, false after opening the provider's $0 checkout.
  claim: (giftId: string, options?: { successUrl?, redirect? }) => Promise<boolean>,
}

// Workspaces + the full invitation flow. Each action rejects with the reason
// when the server refuses it (a 429 with `retryAfter`).
function useOrganization(): {
  organizations, current, member, role, members, isLoading,
  setActive, create, invite, leave,
  acceptInvitation, declineInvitation, cancelInvitation,
  getInvitation, listReceivedInvitations,
}

// Live email delivery status (advances as /email/events webhooks land);
// pauses while the id is undefined or empty.
function useEmailStatus(options: { emailId: MaybeRefOrGetter<string | null | undefined>, api? }): {
  data,       // the full status record — null if unknown, undefined while loading
  status,     // 'queued' | 'sent' | 'delivered' | 'bounced' | … | undefined
  isLoading,
  error,      // why the status could not be read — BackendError | null
  isDelivered,
  isFailed,   // bounced, marked as spam, or failed to send
}

// Experimental. The mail test mode sent to a sandbox address within the last
// hour, live — finish an email-code sign-in in a demo, a preview or an e2e run
// without a real mailbox. Needs `getSandboxInbox` re-exported from
// backend/email.ts; pauses while the address is undefined or empty.
function useSandboxInbox(options: { address: MaybeRefOrGetter<string | null | undefined>, api? }): {
  messages,   // newest first — undefined while loading or without an address
  latest,     // the newest message
  code,       // the first six-digit code in the newest message
  isLoading,
  error,      // BackendError | null
}

// Live durable-workflow status; pauses while the id is null/undefined.
function useWorkflowStatus<Q extends FunctionReference<'query'>>(
  query: Q,   // a query taking { workflowId } that wraps workflow.status
  options: { workflowId: MaybeRefOrGetter<string | null | undefined> },
): {
  data: ComputedRef<FunctionReturnType<Q> | undefined>
  isLoading: ComputedRef<boolean>
  error: ComputedRef<BackendError | null>
}

// Drive a metered, persisted AI token stream (an `ai.stream(...)` definition
// from `nuxt-backend/ai`): `start` reserves credits + drives /ai/stream, the
// text also persists server-side — reload (or pass `streamId`) and it continues.
function useAiStream(
  stream: { start, body },
  options?: { streamId?, url?, headers? },
): { text, status, isStreaming, streamId, error, start: (args?) => Promise<string>, stop }
// error: the refusal of the last start (BackendError | null); a refused start
// ends in status 'error' and rejects.
```

## Better Auth

```ts
function useAuth(initialToken?: string | null): UseBackendAuthService
interface UseBackendAuthService {
  // The base service
  client: AuthClient                       // the Better Auth client — every plugin flow lives here
  session: BetterAuthSession               // Better Auth's own useSession() ref: { data, isPending, error }
  user: ComputedRef<BetterAuthUser | null>
  isAuthenticated: ComputedRef<boolean>
  isLoading: ComputedRef<boolean>
  fetchAccessToken: AuthTokenFetcher
  authVersion: ComputedRef<string | null>
  // Passwordless flows (OTP + passkeys)
  signOut(): Promise<unknown>
  sendOtp(email: string, type?: 'sign-in' | 'email-verification' | 'forget-password'): Promise<unknown>   // 'forget-password' needs emailAndPassword on
  signInWithOtp(args: { email: string, otp: string, name?: string }): Promise<unknown>
  signInWithPasskey(): Promise<unknown>
  registerPasskey(context?: string): Promise<unknown>
  // Account management
  updateUser(args: { name?: string, image?: string | null }): Promise<unknown>
  deleteAccount(): Promise<unknown>     // confirmed by email; a new address goes through useEmailChange
  // Authorization (admin plugin)
  role: ComputedRef<string>                // 'user' when signed out or unset
  hasRole(role: string | string[]): boolean
  can(permissions: Record<string, string[]>): boolean   // e.g. { user: ['ban'] } — sync and local
  banned: ComputedRef<boolean>
}

// The headless state machine behind <AuthForm>: choose → request-code →
// verify-code → add-passkey (email OTP), or choose → signInWithPasskey.
function useLoginFlow(options?: {
  requireName?: boolean                                  // default true — a name field on the email step
  validateEmail?: (email: string) => boolean | string    // false = the default message, a string = your own
  onSuccess?: () => unknown | Promise<unknown>           // signed in and done (e.g. navigateTo)
}): {
  step: Ref<'choose' | 'request-code' | 'verify-code' | 'add-passkey'>
  email: Ref<string>; name: Ref<string>; otp: Ref<string>
  pending: Ref<boolean>; error: Ref<string | null>
  retryAfter: Ref<number | null>      // seconds to wait after a 429; null when unknown or no refusal
  emailValid: ComputedRef<boolean>
  signInWithPasskey: () => Promise<void>
  sendCode: () => Promise<void>       // → 'verify-code'
  verifyCode: () => Promise<void>     // signs in / signs up → 'add-passkey'
  addPasskey: () => Promise<void>     // → done
  skipPasskey: () => Promise<void>    // → done
  goTo: (step: LoginStep) => void
  reset: () => void
}

// The email change, by a code from each address. Like useLoginFlow, it never
// rejects: a failed step stays put with the reason in `error`.
function useEmailChange(options?: {
  validateEmail?: (email: string) => boolean | string    // gates the new address before any code is sent
  onChanged?: (newEmail: string) => unknown | Promise<unknown>
}): {
  step: Ref<'enter-email' | 'confirm-current' | 'verify-new' | 'done'>
  newEmail: Ref<string>; otp: Ref<string>
  pending: Ref<boolean>; error: Ref<string | null>
  retryAfter: Ref<number | null>      // seconds to wait after a 429
  emailValid: ComputedRef<boolean>    // validateEmail passes and it isn't the current address
  start: () => Promise<void>          // a code to the current address → 'confirm-current'
  confirmCurrent: () => Promise<void> // checks it, a code to the new address → 'verify-new'
  verifyNew: () => Promise<void>      // checks it, the account moves → 'done'
  reset: () => void
}

// Passkey management over the auth client. The list loads in the browser
// only (`undefined` during SSR); every method throws a plain Error on
// failure, mirrored into `error`.
function usePasskeys(options?: { immediate?: boolean }): {   // immediate: load on mount, default true
  passkeys: Ref<Passkey[] | undefined>
  isLoading: ComputedRef<boolean>
  error: Ref<string | null>
  refresh: () => Promise<void>
  add: () => Promise<void>
  rename: (id: string, name: string) => Promise<void>
  remove: (id: string) => Promise<void>
}

// Device sessions — a live list (any session age), revoke one, revoke the
// rest. Reads `listSessions` and calls `revokeSession` from backend/auth.ts.
// The list never throws; the actions reject with what the call threw and
// mirror it into `error`.
function useSessions(options?: { api?: SessionsApi }): {
  sessions: ComputedRef<SessionInfo[] | undefined>  // unexpired, latest expiry first; [] when signed out
  current: ComputedRef<SessionInfo | undefined>     // the session belonging to this browser
  isLoading: ComputedRef<boolean>
  isPending: ComputedRef<boolean>                   // an action is running
  error: ComputedRef<BackendError | null>           // the last action's failure, else the list's
  revoke: (id: string) => Promise<void>
  revokeOthers: () => Promise<void>
}

// SessionInfo: { id, userAgent, createdAt, updatedAt, expiresAt, current } — epoch ms

// An agent's request for access, on the consent page: read from the server
// (never from the URL the agent wrote), then approved or denied. Both answers
// send the browser back to the agent.
function useAgentConsent(): {
  request: Ref<AgentConsentRequest | null>
  state: Ref<'loading' | 'ready' | 'missing' | 'answered'>
  isPending: Ref<boolean>
  error: Ref<string | null>
  approve: () => Promise<void>
  deny: () => Promise<void>
}

// AgentConsentRequest: { clientId, clientName (self-declared), redirectHost, scopes, expiresAt }

// 'Chrome · macOS'-style label from a session's userAgent; null when there is
// none or nothing in it is recognisable, so the page words an unknown device.
function describeUserAgent(userAgent?: string | null): string | null

// Unwrap an auth-client `{ data, error }` envelope: returns `data`, or throws
// a plain Error carrying the message when `error` is set (the envelope's
// error is its `cause`, so getBackendError still reads the code).
function unwrapAuth<T>(result: unknown): T

// Read a refusal's catalog code from anything a call threw or resolved with:
// a function's backendError(), the rate limiter's RateLimited error, an auth
// `{ data, error }` result, and any of them as an Error's cause. null otherwise.
// The codes: /docs/reference/backend/errors
function getBackendError(error: unknown): { code, data, message } | null
```

## Low-level auth state (advanced)

```ts
function useAuthState(): ConvexAuthState                       // { isLoading, isAuthenticated }
```

## App config

```ts
// `appConfig.backend` with the package defaults merged in — safe to
// destructure even with a partial app.config; reactive, so HMR flows through.
function useBackendConfig(): {
  billing: { plans: PricingPlan[], packs: CreditPack[], lowCreditsThreshold: number }
  brand: { name?: string, logo?: string }
  labels: { auth?, pricing?, settings?, profile?, security?, history?, usage?, credits? }
}
```

## Auto-registered components

`<Authenticated>`, `<Unauthenticated>` and `<AuthLoading>` come from the base module and render their slot by auth state. `<StorageImage>` is the base module's image from file storage under a neutral name: `get-url` (the public `{ storageId }` → URL query) and `storage-id` props, a default slot while it loads and a `missing` slot when the file is gone. The package registers the rest:

<table>
<thead>
  <tr>
    <th>
      Component
    </th>
    
    <th>
      Props (default)
    </th>
    
    <th>
      Emits
    </th>
    
    <th>
      Slots
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        <AuthForm>
      </code>
    </td>
    
    <td>
      <code>
        title
      </code>
      
       (<code>
        'Sign in'
      </code>
      
      ), <code>
        requireName
      </code>
      
       (<code>
        true
      </code>
      
      ), <code>
        validateEmail
      </code>
    </td>
    
    <td>
      <code>
        success
      </code>
      
      , <code>
        error(message)
      </code>
      
      , <code>
        step(step)
      </code>
    </td>
    
    <td>
      <code>
        header
      </code>
      
      , <code>
        error
      </code>
      
      , <code>
        footer
      </code>
      
      , and one per step — <code>
        choose
      </code>
      
      , <code>
        request-code
      </code>
      
      , <code>
        verify-code
      </code>
      
      , <code>
        add-passkey
      </code>
      
      ; each receives the <code>
        useLoginFlow()
      </code>
      
       object
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <RoleBoundary>
      </code>
    </td>
    
    <td>
      <code>
        role
      </code>
      
       (string or array — any match), <code>
        permission
      </code>
      
       (<code>
        { user: ['ban'] }
      </code>
      
      )
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        default
      </code>
      
      , <code>
        fallback
      </code>
      
       (not allowed), <code>
        placeholder
      </code>
      
       (session loading)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <FeatureBoundary>
      </code>
    </td>
    
    <td>
      <code>
        feature
      </code>
      
       (string or array), <code>
        plan
      </code>
      
       (product id or array)
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        default
      </code>
      
      , <code>
        fallback
      </code>
      
      , <code>
        placeholder
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <OrganizationBoundary>
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        default
      </code>
      
       (<code>
        { workspace }
      </code>
      
      ), <code>
        fallback
      </code>
      
       (no active workspace), <code>
        placeholder
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <AcceptInvitation>
      </code>
    </td>
    
    <td>
      <code>
        invitationId
      </code>
      
       (the <code>
        ?id=
      </code>
      
       query parameter), <code>
        activate
      </code>
      
       (<code>
        true
      </code>
      
      )
    </td>
    
    <td>
      <code>
        accepted(invitation)
      </code>
      
      , <code>
        declined(invitation)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      one per state — <code>
        loading
      </code>
      
      , <code>
        ready
      </code>
      
      , <code>
        accepted
      </code>
      
      , <code>
        declined
      </code>
      
      , <code>
        missing
      </code>
      
      , <code>
        error
      </code>
      
      ; each receives <code>
        { invitation, accept, decline, pending, error }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <AgentConsent>
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        answered(approved)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      one per state — <code>
        loading
      </code>
      
      , <code>
        ready
      </code>
      
      , <code>
        missing
      </code>
      
      , <code>
        answered
      </code>
      
      ; each receives <code>
        { request, approve, deny, pending, error }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <GiftClaimBanner>
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        claimed(count)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        default
      </code>
      
       (<code>
        { gifts, claim, isClaiming }
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <BillingCheckoutLink>
      </code>
    </td>
    
    <td>
      <code>
        productIds
      </code>
      
      , <code>
        subscriptionId
      </code>
      
      , <code>
        metadata
      </code>
      
      , <code>
        locale
      </code>
      
      , <code>
        discountCode
      </code>
      
      , <code>
        utm
      </code>
      
      , <code>
        successUrl
      </code>
      
       (the current page), <code>
        redirect
      </code>
      
       (<code>
        true
      </code>
      
      )
    </td>
    
    <td>
      <code>
        error(error)
      </code>
    </td>
    
    <td>
      <code>
        default
      </code>
      
       (<code>
        { pending }
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <BillingPortalLink>
      </code>
    </td>
    
    <td>
      <code>
        returnUrl
      </code>
      
       (the current page), <code>
        redirect
      </code>
      
       (<code>
        true
      </code>
      
      )
    </td>
    
    <td>
      <code>
        error(error)
      </code>
    </td>
    
    <td>
      <code>
        default
      </code>
      
       (<code>
        { pending }
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <PricingTable>
      </code>
    </td>
    
    <td>
      <code>
        plans
      </code>
      
      , <code>
        packs
      </code>
      
       (<code>
        appConfig.backend.billing.*
      </code>
      
      ), <code>
        title
      </code>
      
      , <code>
        period
      </code>
      
       (<code>
        '/mo'
      </code>
      
      ), <code>
        showPacks
      </code>
      
       (<code>
        true
      </code>
      
      ), <code>
        showPortal
      </code>
      
       (<code>
        true
      </code>
      
      ), <code>
        redirect
      </code>
      
       (<code>
        true
      </code>
      
      )
    </td>
    
    <td>
      <code>
        checkout(url)
      </code>
      
      , <code>
        plan-changed(key)
      </code>
      
      , <code>
        canceled
      </code>
      
      , <code>
        uncanceled
      </code>
      
      , <code>
        topped-up(key)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        header
      </code>
      
      , <code>
        plan
      </code>
      
      , <code>
        plan-action
      </code>
      
      , <code>
        packs
      </code>
      
      , <code>
        pack
      </code>
      
      , <code>
        portal
      </code>
      
      , <code>
        empty
      </code>
      
      , <code>
        error
      </code>
      
      , <code>
        footer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <BillingHistory>
      </code>
    </td>
    
    <td>
      <code>
        title
      </code>
      
      , <code>
        limit
      </code>
      
       (the provider's page size), <code>
        invoices
      </code>
      
       (<code>
        true
      </code>
      
      ), <code>
        redirect
      </code>
      
       (<code>
        false
      </code>
      
      )
    </td>
    
    <td>
      <code>
        invoice(url)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        header
      </code>
      
      , <code>
        order
      </code>
      
      , <code>
        loading
      </code>
      
      , <code>
        empty
      </code>
      
      , <code>
        error
      </code>
      
      , <code>
        pager
      </code>
      
      , <code>
        footer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <UsageHistory>
      </code>
    </td>
    
    <td>
      <code>
        meter
      </code>
      
       (every metered event), <code>
        title
      </code>
      
      , <code>
        limit
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      <code>
        header
      </code>
      
      , <code>
        event
      </code>
      
      , <code>
        loading
      </code>
      
      , <code>
        empty
      </code>
      
      , <code>
        error
      </code>
      
      , <code>
        pager
      </code>
      
      , <code>
        footer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <CreditsLowBanner>
      </code>
    </td>
    
    <td>
      <code>
        meter
      </code>
      
       (the primary meter), <code>
        threshold
      </code>
      
       (<code>
        appConfig.backend.billing.lowCreditsThreshold
      </code>
      
      ), <code>
        pack
      </code>
      
      , <code>
        dismissible
      </code>
      
       (<code>
        true
      </code>
      
      ), <code>
        redirect
      </code>
      
       (<code>
        true
      </code>
      
      )
    </td>
    
    <td>
      <code>
        dismissed
      </code>
      
      , <code>
        topped-up(url)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        default
      </code>
      
      , <code>
        message
      </code>
      
      , <code>
        action
      </code>
      
      , <code>
        dismiss
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <WorkspaceSettings>
      </code>
    </td>
    
    <td>
      <code>
        sections
      </code>
      
       (<code>
        ['workspaces', 'billing', 'credits']
      </code>
      
      ), <code>
        pricingPath
      </code>
      
       (the resolved pricing page)
    </td>
    
    <td>
      <code>
        workspace-created(workspace)
      </code>
      
      , <code>
        workspace-switched(id)
      </code>
      
      , <code>
        plan-canceled
      </code>
      
      , <code>
        topped-up(key)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        workspaces
      </code>
      
      , <code>
        workspace
      </code>
      
      , <code>
        create
      </code>
      
      , <code>
        billing
      </code>
      
      , <code>
        credits
      </code>
      
      , <code>
        footer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <ProfileSettings>
      </code>
    </td>
    
    <td>
      <code>
        validateEmail
      </code>
      
      , <code>
        showIdentity
      </code>
      
       (<code>
        true
      </code>
      
      )
    </td>
    
    <td>
      <code>
        name-saved(name)
      </code>
      
      , <code>
        email-changed(email)
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        identity
      </code>
      
      , <code>
        name
      </code>
      
      , <code>
        email
      </code>
      
      , <code>
        footer
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        <SecuritySettings>
      </code>
    </td>
    
    <td>
      <code>
        sections
      </code>
      
       (<code>
        ['passkeys', 'sessions', 'danger']
      </code>
      
      ), <code>
        confirm
      </code>
      
       (<code>
        window.confirm
      </code>
      
      )
    </td>
    
    <td>
      <code>
        passkey-added
      </code>
      
      , <code>
        passkey-removed(id)
      </code>
      
      , <code>
        session-revoked(id)
      </code>
      
      , <code>
        sessions-revoked
      </code>
      
      , <code>
        delete-requested
      </code>
      
      , <code>
        signed-out
      </code>
      
      , <code>
        error(message)
      </code>
    </td>
    
    <td>
      <code>
        passkeys
      </code>
      
      , <code>
        passkey
      </code>
      
      , <code>
        sessions
      </code>
      
      , <code>
        session
      </code>
      
      , <code>
        danger
      </code>
      
      , <code>
        footer
      </code>
    </td>
  </tr>
</tbody>
</table>

`<CreditsLowBanner>` renders nothing, slots included, until the balance has loaded and sits below `threshold`, and nothing again once dismissed.

Every slot of the pricing, history, banner and settings components receives the component's full slot context — the composables it runs on plus its pending and error state; [Customize with slots](https://nuxt-backend.dev/docs/ui/customize/slots) has a worked example.

The module also mounts the [ready-made pages](https://nuxt-backend.dev/docs/ui/pages) on these components — `/accept-invitation` (module option `pages.acceptInvitation`), for one, renders `<AcceptInvitation>` behind the `auth` middleware.


## Sitemap

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