Usage

Creating Plans

Go to Headcount → Plans and create a tier. Each plan maps to:

  • A Stripe Price — auto-created on first sync, or paste an existing Price ID
  • A Craft user group — members are added on subscription, removed on cancellation
  • A billing interval (day, week, month, year), price, currency, and optional trial period
  • An optional features list for use on your pricing page

Plans come in two shapes. A recurring plan bills on each member’s own anniversary and renews until cancelled — the default, and what the fields above describe. A fixed season plan is one payment for a calendar window every member shares, so everybody expires on the same day whenever they joined. See Season Memberships.

Content Gating

Go to Headcount → Access Rules to gate content. Rules can target a section, entry type, category, or individual entry. Choose how locked content responds:

BehaviorEffect
Redirect302 redirect to your login or pricing page
PaywallShow truncated teaser content with a CTA
HideReturn 404 for unauthorized users

Gating hooks into Entry::EVENT_AUTHORIZE_VIEW at the system level, so it works regardless of template code.

Drip Content

Define drip schedules to release entries N days after a member’s subscription start. Use the helpers below in templates to show progress, countdowns, or alternate content.

Checkout in Templates

Render a pricing page with a checkout form for each plan:

{# Pricing page #}
{% for plan in craft.headcount.plans() %}
  <div class="plan">
    <h3>{{ plan.name }}</h3>
    <p>{{ plan.currency|upper }} {{ plan.price|number_format(2) }}/{{ plan.billingInterval }}</p>
    {% if plan.features %}
      <ul>
        {% for feature in plan.features %}
          <li>{{ feature }}</li>
        {% endfor %}
      </ul>
    {% endif %}
    <form method="post">
      {{ csrfInput() }}
      <input type="hidden" name="action" value="headcount/checkout/create-session">
      <input type="hidden" name="planHandle" value="{{ plan.handle }}">
      <button type="submit">Subscribe</button>
    </form>
  </div>
{% endfor %}

Twig Variable: craft.headcount

{# Check if current user has any active subscription #}
{% if craft.headcount.isSubscribed() %}

{# Check specific plan #}
{% if craft.headcount.isSubscribed('pro-monthly') %}

{# Check if user can access an entry (respects gating + drip) #}
{% if craft.headcount.canAccess(entry) %}

{# Get current user's active subscriptions #}
{% set subs = craft.headcount.subscriptions() %}

{# Get all enabled plans #}
{% set plans = craft.headcount.plans() %}

{# Get a specific plan #}
{% set pro = craft.headcount.plan('pro-monthly') %}

{# Checkout URL (form POST is preferred) #}
{{ craft.headcount.checkoutUrl('pro-monthly') }}

{# Customer Portal URL #}
{{ craft.headcount.portalUrl() }}

{# Drip content checks #}
{% if craft.headcount.isUnlocked(entry) %}
{{ craft.headcount.unlocksIn(entry) }}  {# days remaining #}

{# Coupon input field helper #}
{{ craft.headcount.couponField() }}

{# Wallet cards — each returns null when that platform isn't configured #}
{% if craft.headcount.walletEnabled() %}
  {{ craft.headcount.appleWalletUrl(subscription) }}
  {{ craft.headcount.googleWalletUrl(subscription) }}
{% endif %}

Twig Tag: {% headcountGate %}

{# Gate content to any active subscriber #}
{% headcountGate %}
  <p>Members-only content here.</p>
{% endheadcountGate %}

{# Gate to a specific plan #}
{% headcountGate planHandle='pro-monthly' %}
  <p>Pro content here.</p>
{% endheadcountGate %}

Native Craft User Group Checks

Because Headcount syncs subscriptions to user groups, you can use Craft’s native checks:

{% if currentUser and currentUser.isInGroup('pro') %}
  <p>Pro member content</p>
{% endif %}

Member Portal

Send members to the Stripe Customer Portal to update their card, change plans, or cancel:

<a href="{{ craft.headcount.portalUrl() }}">Manage your subscription</a>

CLI Commands

# Retire memberships past their end date — cancels those the member cancelled,
# expires finished season terms
php craft headcount/subscriptions/expire

# Email members whose membership runs out soon (once per term)
php craft headcount/subscriptions/remind

# Sync all active subscription statuses from Stripe/PayPal
php craft headcount/subscriptions/sync

# Sync plans to payment providers (create Stripe Products/Prices)
php craft headcount/sync/plans

# Check sync health and display stats
php craft headcount/sync/status

Nothing schedules these for you. Run headcount/subscriptions/expire and headcount/subscriptions/remind daily, and headcount/subscriptions/sync hourly, via cron. The reminder matters most for season memberships, which do not renew on their own.

Events

Hook subscription lifecycle from a custom module or plugin:

use justinholtweb\headcount\services\Subscriptions;
use justinholtweb\headcount\events\SubscriptionEvent;
use yii\base\Event;

Event::on(
    Subscriptions::class,
    Subscriptions::EVENT_AFTER_CREATE_SUBSCRIPTION,
    function (SubscriptionEvent $event) {
        $subscription = $event->subscription;
        // Custom logic after subscription creation
    }
);

Available events on the Subscriptions service:

  • EVENT_BEFORE_CREATE_SUBSCRIPTION / EVENT_AFTER_CREATE_SUBSCRIPTION
  • EVENT_BEFORE_UPDATE_SUBSCRIPTION / EVENT_AFTER_UPDATE_SUBSCRIPTION
  • EVENT_BEFORE_CANCEL_SUBSCRIPTION / EVENT_AFTER_CANCEL_SUBSCRIPTION

More in Site Features

Pairs well with Headcount

The bigger pieces a client asks for by name. Events, bookings, memberships, courses, reviews, a support desk, job listings and the rest.