Skip to main content
SiteShi p

Reference

Membership & Gated Content

Access control is enforced at the hosted runtime: non-members get a 401 (or login redirect) before the page is rendered. The engine surfaces isMember and memberTier to your templates so logged-in members can see personalized content. Local preview does not enforce auth — gated pages render as if the viewer is a member, so you can author them.

Access control is enforced before the page is rendered — not just hidden in HTML. A non-member requesting a gated page gets a 401 response (or redirect to a login page), not a page with hidden content. This means gating is secure even if someone inspects the source.

How it works

  1. Set access: in page frontmatter → Worker enforces it on every request
  2. Use {% if isMember %} blocks in templates → show/hide content based on membership
  3. memberTier is available for multi-tier gating

1. Gate an entire page

Add access: to the page frontmatter. The Worker checks the user's JWT before rendering:

---
title: Member Dashboard
description: Your exclusive member area
access: member
---
<h1>Welcome back, member!</h1>
<p>Here's your exclusive content...</p>

Access values:

Value Who can access
member Any logged-in member (any tier)
pro Members with tier pro or higher
enterprise Members with tier enterprise only
* Any authenticated user (any tier, even free)

Non-members are redirected to the login page automatically.

2. Partial gating — show different content to members vs guests

Use {% if isMember %} blocks anywhere in a page or template. The Worker injects isMember (boolean) and memberTier (string) into every page render:

---
title: Advanced Guide to Investing
---

<h1>Advanced Guide to Investing</h1>

<!-- Free preview — everyone sees this -->
<div class="prose">
  <p>Investing can seem complex, but with the right framework...</p>
  <h2>The basics</h2>
  <p>Start with an emergency fund before investing...</p>
</div>

{% if isMember %}
<!-- Full content — members only -->
<div class="prose mt-8">
  <h2>Advanced strategies</h2>
  <p>Once you have the basics covered, consider...</p>
</div>
{% else %}
<!-- Upgrade prompt — guests see this -->
<div class="bg-primary-50 border border-primary-200 rounded-xl p-8 mt-8 text-center">
  <h2 class="text-xl font-bold mb-2">Continue reading with a membership</h2>
  <p class="text-secondary-600 mb-6">Get access to the full guide plus all premium content.</p>
  <a href="/join" class="bg-primary-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-primary-700">
    Join now
  </a>
</div>
{% endif %}

3. Tier-based gating

memberTier holds the member's tier string (e.g., "free", "pro", "enterprise"):

{% if memberTier == "pro" or memberTier == "enterprise" %}
<div>Pro-only feature...</div>
{% elsif isMember %}
<div class="p-6 bg-secondary-50 rounded-lg text-center">
  <p class="font-semibold">Upgrade to Pro to unlock this feature</p>
  <a href="/upgrade" class="text-primary-600 font-medium mt-2 inline-block">Upgrade →</a>
</div>
{% endif %}

4. Gated pages directory pattern

For a members area with multiple pages, gate the whole section with access: on each page:

pages/
  members/
    index.html       ← access: member
    dashboard.html   ← access: member
    courses.html     ← access: member
    billing.html     ← access: member

Template variables

Variable Type Value
{{ isMember }} boolean true if user is authenticated with any membership
{{ memberTier }} string Tier name ("free", "pro", "enterprise") or empty

Both are available on every page — no configuration needed. On public pages where no one is logged in, isMember is false and memberTier is empty.

Rules

  • access: gating is server-side — don't try to replicate it with Alpine.js x-show or CSS hidden. The Worker enforces it before the page renders.
  • {% if isMember %} blocks are for partial gating — showing a teaser to guests while showing full content to members. For full page gating, use access: frontmatter instead.
  • Always provide an upgrade/login prompt in the {% else %} branch — don't just show nothing
  • memberTier comparison is case-sensitive — match the exact strings used when creating members
  • Membership setup (Stripe Subscriptions, login flow) is handled separately — this skill covers the template side only

Found something out of date? Open an issue. ← All docs