Supabase Billing (supabase-billing)
supabase-billing is a provider-agnostic subscription and entitlement layer for Rails apps on Supabase — Stripe (web) and Adapty (mobile) ingestion adapters write into one canonical schema and a single `user.entitled?` API.
supabase-billing is the Ruby on Rails subscription and entitlement layer for Supabase. It ships a canonical billing schema (customers, subscriptions, plans, entitlements, usage limits), ingestion adapters for Stripe and Adapty, and a single entitled? API for gating features — so your app code never reaches for a provider SDK to ask "can this user use this feature?".
The gem builds on supabase-rails and assumes Supabase Auth is your identity layer: Current.user is set per request, RLS policies key off auth.uid(), and provider customer records are linked to auth.users.id automatically.
Starting from a starter kit?
supabase-billing is most commonly paired with the Hotwire monolith starter kit — it ships supabase-rails in :web mode with a full server-rendered app shell where entitled? gating drops naturally into controllers and views. The Inertia + React kit is also :web mode and works the same way on the Rails side; expose entitled? results through inertia_share to gate React routes.
Why supabase-billing
- Provider-agnostic by design. Your app calls
user.entitled?(:pro_export)whether the subscription was opened on the web via Stripe or in your iOS / Android app via Adapty. - One canonical schema, two adapters. Webhooks from each provider land in the same
customers/subscriptions/subscription_itemstables. Reporting, RLS, and feature gates see one shape. - RLS-aware out of the box. Tables ship with policies scoped to
auth.uid(), so a misconfigured controller can't leak another user's subscription. - No reimplementation of Supabase plumbing. Session storage, JWT verification, and per-request auth come from
supabase-rails. This gem only adds billing.
Providers
Stripe (web)
Webhook ingestion (default) or stripe-sync-engine mode reading from the stripe.* schema. Both produce the same canonical state.
Adapty (mobile)
One server-to-server webhook adapter for iOS and Android in-app subscriptions — no StoreKit or Play Billing parsing in your app.
The entitled? API
Once a webhook has landed, gating a feature is a single call:
class ExportsController < ApplicationController
before_action :authenticate
def create
return head :payment_required unless Current.user.entitled?(:pro_export)
Export.create!(user: Current.user, format: params[:format])
end
endentitled? resolves the current user's active subscription, looks up the plan's entitlements, and answers true/false. Companion methods cover the common shapes — Current.user.subscribed?, Current.user.plan, and Current.user.limit(:monthly_exports) for metered features. See Entitlements for the full surface.
Dependency on supabase-rails
supabase-billing is a Rails engine that depends on supabase-rails. Before installing this gem, your app must already be wired up via rails g supabase:install and have a User model generated by rails g supabase:user_model (so users.id matches auth.users.id). The Stripe and Adapty webhook controllers reuse the Supabase request context that supabase-rails sets, and entitlement queries trust that auth.uid() is populated. The Getting Started guide walks through the required supabase-rails setup first.
Explore the reference
Getting Started
bundle add supabase-billing, run the install generator, migrate, and make your first entitled? call.
Providers
Stripe (webhook and sync-engine modes) and Adapty (mobile) — endpoints, secrets, and configuration.
Entitlements
entitled?, subscribed?, plan, and limit — gating controllers, views, and background jobs.
Schema Reference
Canonical tables, provider mapping tables, and the RLS model — with an ER diagram.
Webhooks
Mounted endpoints, signature verification, idempotency, and replay behavior for each provider.
Project
- Source:
supabase-community/supabase-billing - Rails integration reference:
supabase-railsdocs - Issues: the gem's issue tracker