Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Subscriptions Laravel Package

flarum/subscriptions

Flarum Subscriptions lets users follow discussions and tags, receive notifications about new activity, and manage their subscriptions from a dedicated page. It adds flexible subscription controls to help communities keep members engaged with relevant content.

View on GitHub
Deep Wiki
Context7

Getting Started

flarum/subscriptions is the bundled Flarum extension that adds Stripe-backed paid subscriptions and ties them to group membership. The repository is a read-only subtree split of flarum/framework; do not open issues or PRs here — everything goes to the monorepo.

  1. Confirm it's present. It ships with a default Flarum install:
    composer show flarum/subscriptions
    # if missing:
    composer require flarum/subscriptions
    
  2. Enable it at /admin#/extensions → Subscriptions, then clear cache (php flarum cache:clear).
  3. Configure Stripe in the extension's settings pane: publishable key, secret key, webhook signing secret. The pane shows the exact webhook URL to register in the Stripe dashboard (or forward locally with stripe listen --forward-to <url>).
  4. Create your first plan in the admin Subscription Plans page: name, description, price, billing interval, Stripe product/price IDs, and the Flarum group the plan grants.
  5. Gate content with that group. Create a tag, then in tag permissions grant the subscription group viewDiscussions / startDiscussion. That is the whole first use case: a paid members-only category, with zero custom code.

Where to look first

  • src/SubscriptionPlan.php, src/Subscription.php — the two models that matter.
  • src/Api/ — SubscriptionPlanResource, SubscriptionResource serializers.
  • src/Listeners/ (or src/Webhook/) — Stripe webhook handling; this is where group sync happens.
  • migrations/ — table shapes before you add columns.
  • js/src/forum/ — plan list, checkout flow, user subscription menu; js/src/admin/ — plan CRUD.

Implementation Patterns

Gate by group, not by subscription record. Access checks should stay in Flarum permissions ($actor->hasPermission('tag5.viewDiscussions')), because the extension's job is to keep users_groups in sync with Stripe state. Building your own Subscription::where(...) checks duplicates that logic and drifts.

Expose extra plan data to the frontend. Add a migration on subscription_plans, then extend the serializer:

new Extend\ApiSerializer(SubscriptionPlanResource::class)(function ($serializer, $plan, $attributes) {
    $attributes['badgeColor'] = $plan->badge_color;
})

Remember to add the column to the model's $casts/accessors and to the admin form component in js/src/admin.

Grant comped subscriptions deliberately. Creating a Subscription row alone does not add the user to the group; group membership is what grants access. Either add the user to the group directly (and accept that Stripe has no record), or create a Stripe subscription with a 100%-off coupon so webhooks keep it consistent. Pick one and document it — mixed approaches produce users who lose access on the next webhook.

Reconcile with a scheduled command. Webhooks get lost. A nightly job that pulls active subscriptions from Stripe and diffs against local subscriptions rows (and group membership) catches nearly all support tickets:

Schedule::command('subscriptions:reconcile')->daily();

React to lifecycle changes in listeners. Listen for the extension's subscription events (check src/ for the exact event classes in your version) or, more robustly, hook your own listener onto the webhook handler output. Keep listeners queued — webhook responses must be fast or Stripe retries.

Frontend integration. Wrap existing components rather than replacing them:

extend(IndexPage.prototype, 'sidebarItems', (items) => {
  if (!app.session.user?.groups.some(g => g.id() === SUB_GROUP_ID)) return items;
  items.add('member-badge', <span className="badge">Supporter</span>, 5);
});

Note that app.session.user.groups is stale until reload after a webhook grants access.

Local testing loop. Stripe CLI forwards events; use stripe trigger customer.subscription.updated and test clocks for renewal/cancellation paths. Verify group add on checkout.session.completed, group removal on customer.subscription.deleted, and no removal on cancel_at_period_end.

Gotchas and Tips

  • Read-only repo. 13 stars is a mirror artifact, not a quality signal. All patches must target flarum/framework; local fixes belong in your own extension.
  • Webhook signature failures are the #1 install problem: wrong signing secret, a proxy stripping the raw body, or a URL that redirects (http→https). Stripe retries for days, so a misconfigured endpoint silently looks fine until subscriptions desync.
  • Settings are cached. Changing Stripe keys directly in the settings table or via env requires php flarum cache:clear, otherwise the old key keeps validating webhooks.
  • Group removal is manual and dangerous. If an admin removes a user from a subscription group in the ACP, the Stripe subscription still bills them. Conversely, manually adding a user to the group gives free permanent access. There is no "unmanaged membership" flag — audit group changes.
  • past_due and unpaid states are the fuzzy middle. Decide explicitly whether they keep access; the default webhook handling in your version may differ from expectations. Test with Stripe test clocks rather than waiting a month.
  • Deleting or deactivating a plan hides it from new buyers but existing subscriptions keep their group. Deleting a plan with attached subscriptions can orphan local records — prefer deactivation.
  • Currency is global, set in Flarum core settings, and is not per-plan. Zero-decimal currencies (JPY) and multi-currency pricing are not handled — Stripe Tax and multi-currency need custom work.
  • No tax, invoicing, or dunning UI ships with the extension. If you need EU VAT or failed-payment emails, that's Stripe's dashboard plus your own listeners.
  • Permission caching within a request. After granting a group in a listener, call $user->unsetRelation('groups') (or reload the actor) before checking permissions in the same request, or you'll evaluate against stale groups.
  • Don't extend Subscription casually. Its rows are keyed to Stripe IDs (stripe_id, stripe_status). Custom columns are fine; reinterpreting status values breaks the sync logic.
  • Localization keys live under flarum-subscriptions.forum.* and flarum-subscriptions.admin.*. Translation packs that predate your Flarum version may miss newer keys.
  • Conflict surface: other payment/membership extensions (fof/-namespaced) that also mutate group membership will fight this one. Pick a single source of truth for group assignment.
  • Extension points are thin. There is no payment-provider interface — it's Stripe-only by design. Supporting Paddle/PayPal means forking the checkout controller and webhook listener into your own extension, not configuring this one.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core