Memberships
Memberships let organisers sell recurring access to their community — tiers with billing cadences, optional approval, a members roster, and renewals — without building a separate membership stack.
This guide is for organisers setting up join, tiers, approval, roster, and renewals. Application-form field details are covered lightly here; a dedicated membership-forms doc will go deeper.
Set up tiers
Each seller can define one or more membership tiers. A tier can have one or more billing cadences (for example monthly or annual).
For each cadence you configure:
- The amount the seller nets (
amount_cents) - A linked Stripe Price (buyers pay the grossed-up amount including fees)
You can also mark a tier as requires approval. When that is on, applicants save a card and wait for you to approve or reject before billing starts.
When Prices re-sync
- Manually from the tiers panel for a tier
- Automatically after you edit the seller fee split
- Automatically after a platform admin edits membership fees
Instant join vs approval
Instant join creates a Stripe subscription and charges the first invoice immediately. Checkout is card-only (Apple Pay and Google Pay are not offered for memberships).
Approval join (when the tier requires approval):
- The applicant completes a $0 SetupIntent — their card is saved, not charged.
- The request moves through
awaiting_card→pending→approving→approvedorrejected. - After the card is saved successfully, the request is pending and both sides get email.
- Approve creates the Stripe subscription (billing from the approval date) and the memberships row.
- Reject detaches the card; the applicant is never charged.
Manage the queue on /dashboard/members.
⚠️ While a request is in approving, reject is blocked to avoid a race with subscription creation. The Stripe Price ID locked at apply time is what approval uses — even if you re-synced the tier afterward.
Application forms
Each tier can have its own application form (a list of fields). Supported types include text, textarea, option, checkbox, email, phone, date, number, and URL.
Fields can be:
- Visible to the buyer — filled at join
- Seller-only (hidden) — the buyer never sees them; you fill them from the roster; they appear in CSV export. Required is forced off for hidden fields at normalize time.
Buyer submit only accepts visible fields; hidden answers from the buyer are rejected. Answers are stored per seller, user, and tier. Seller-only fields apply to Stripe members; imported-member CSV seller-only fields are out of scope for this flow.
Members roster
Open /dashboard/members to see active Stripe subscriptions and imported members. Incomplete and incomplete_expired Stripe statuses are hidden by default.
Imported members appear as read-only. An admin can Remove an imported row. Import history (batch success / skip / fail) lives on /dashboard/import-members.
Export
Editors and above can export members (Stripe + imported + resolved form answers). The CSV includes fixed columns plus one column per form question. Exports cap at 3,000 rows and set a truncated flag when the cap is hit. Roster size is also bounded (up to 10,000 seller membership rows).
Renewals and reminders
- Daily at 07:00 UTC: renewal reminders for Stripe members 7 days before renewal
- Daily cron at 07:15 UTC sends the weekly imported-expiry digest (imported members expiring within 7 days)
Reminders are idempotent per row. Sellers can opt out with membership reminders on /dashboard/notifications. Non-production environments never send these emails.
Members who want to stop auto-renewal use Turn off auto-renewal, which sets cancel-at-period-end. They keep access until the period ends.
Email members
In /dashboard/marketing/send-email, you can target Members, optionally filtered by tier. The Resend segment is reconciled at send time and reuses the seller’s Resend topic. Imported members are opted in on first sight; existing opt-outs are never overwritten. The marketing checkbox defaults to unchecked.
External memberships
If you already run memberships elsewhere, enable external membership (external_membership_enabled) and set an external URL. The storefront can show Become a member even with zero internal tiers; CTAs open the external URL in a new tab. You can still keep internal tier config for member discounts.
Member pricing for signed-in buyers uses email / Clerk identity — there is no “checkout by membership number” field for typing an external ID at checkout.
What members see
- Default join CTA label is Join now (external memberships keep Become a member)
- On their account page, members see imported memberships; signing in can claim a row via
claimed_by_user_id - Seller description HTML is stripped when displayed
Good to know
- Fee changes do not rewrite existing Stripe subscription Prices until the member changes plan
- Approval uses the Price locked when the applicant applied
- Do not expect card-update-after-approval flows, or seller-only fields on imported CSV import, to be documented here — those are out of scope for this page