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:
| Behavior | Effect |
|---|---|
| Redirect | 302 redirect to your login or pricing page |
| Paywall | Show truncated teaser content with a CTA |
| Hide | Return 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_SUBSCRIPTIONEVENT_BEFORE_UPDATE_SUBSCRIPTION/EVENT_AFTER_UPDATE_SUBSCRIPTIONEVENT_BEFORE_CANCEL_SUBSCRIPTION/EVENT_AFTER_CANCEL_SUBSCRIPTION