Subscriptions

Subscriptions app

Show plans, start a checkout, guard pages by plan and turn on the ready-made membership pages.

This page covers the app side: the composables for your own pricing page, a middleware that guards pages by plan, the components and the ready-made pages. It is for app developers, in server mode. Set up the module first: Overview.

Composables

Both composables are auto-imported, and exported from @frigear-nu/subscriptions/composables.

usePlans(): { plans, status, loading, error, refresh }   // plans sorted by rank
useSubscription(): {
  subscription, plan, status, loading, loadError, isActive, cancelsAt,
  has(planKey: string): boolean,
  pending, error, refresh,
  subscribe(planKeyOrPriceId: string, options?: { successUrl?: string, cancelUrl?: string }): Promise<CheckoutResponse>,
  openPortal(returnUrl?: string): Promise<void>,
}

Both load through useCachedFetch, so their data survives navigation. Plans are cached for five minutes, and the subscription for one minute.

  • loading is true only while nothing is loaded yet. Render a placeholder for it, not the "no subscription" state.
  • loadError is the error of loading the subscription. error and pending belong to subscribe and openPortal.
  • subscribe sends a string that starts with price_ as priceId, and anything else as planKey. It redirects to Stripe Checkout, or refreshes when an existing subscription was changed.
  • openPortal redirects to the billing portal. returnUrl defaults to the current page.

Middleware

The subscription route middleware sends people without the right plan away from a page:

definePageMeta({
  middleware: 'subscription',
  subscription: { plans: ['member'], redirectTo: '/join' }, // both optional
})
  • It redirects to redirectTo, else to the membership page, with the original route in ?redirect=. Without either target it does nothing.
  • Visitors and users without an active plan are redirected.
  • A subscription on a price outside every configured plan has no activePlan, so it is redirected too.
The middleware guards navigation only. Check the plan in every API route with hasActiveSubscription.

Components

SubscriptionPlans and SubscriptionStatus are built on @nuxt/ui and expose scoped slots.

SubscriptionPlans propTypeDefaultPurpose
localestring–The formatting locale.
currencystringthe price'sOverrides the currency for formatting.
interval'day' | 'week' | 'month' | 'year'the first priceWhich price to show per plan.
plansstring[]allShows only these plan keys.
confirmChangebooleanfalseAsks before changing an existing subscription.
mode'auto' | 'list' | 'tabs''auto'auto shows tabs on small screens and cards side by side from sm up.
highlightstringthe current planThe plan key to emphasise.

SubscriptionPlans emits select(plan, price). Each plan has a state: 'current', 'upgrade', 'downgrade' or 'subscribe'. A visitor who picks a plan is sent to the Users register page, with ?redirect=<membership page>?plan=<key>.

ComponentSlotSlot props
SubscriptionPlansloading, empty–
header, badge{ plan, state }
price{ plan, price, formatPrice, intervalLabel }
features{ plan }
action{ plan, state, pending, select }
confirm{ plan, price }
confirm-actions{ plan, price, confirm }
SubscriptionStatusheader, loading, empty– (prop locale)
plan{ plan, label, subscription }
badge{ subscription, badge }, where badge is { status, label, color, icon }
dates{ subscription, cancelsAt, dateText }
actions{ manage, resubscribe, cancelling, pending }

UsersAdminMembership (prop user, emits refresh) is the panel in the admin user tab where staff grant and revoke a free membership.

Pages and menu

Each page you turn on in settings.pages is added at its path, unless a page already exists there.

PageDefault pathWhat it shows
Membership/account/membershipThe current subscription, or a "No membership yet" state, then the plans.
Success/account/membership/successChecks again every 3 seconds, for about 2 minutes, until the webhook has arrived.
Cancel/account/membership/cancelA way back to the membership page.

The pages sit under /account/**, so they use the account layout. They follow the UX patterns for account pages.

Other modules add more when they are installed:

  • Menu, with the membership page on: a Membership item (id membership, order 15) in the account menu.
  • Widget: a membership summary in the account.overview widget area.
  • Widget and Admin: a Membership tab (?tab=membership, order 10) and a Current membership card on the admin user page. They sit in the admin.users tab area and the admin.users.overview widget area. Revoking a free membership asks for confirmation first.

Next steps

Copyright © 2026