Restrict content and sell access with Stripe. The first membership plugin for EmDash CMS.
- Content Restriction — Restrict any post or page to require a Stripe product purchase
- Stripe Connect — One-click connection to your Stripe account via Stripe Connect OAuth
- Checkout — Redirect visitors to Stripe Checkout to purchase access
- Customer Portal — Let customers manage their subscriptions via Stripe's billing portal
- Magic Link Login — Returning customers log in via email link (no passwords)
- Admin UI — Settings page, restrictions manager, dashboard widget, and toolbar button in the content editor
- Real-time Access Checks — Verifies purchases against the Stripe API on every page load. No stale local data.
npm install emdash-restrict-with-stripe// astro.config.mjs
import { restrictWithStripe } from "emdash-restrict-with-stripe";
export default defineConfig({
integrations: [
emdash({
plugins: [restrictWithStripe()],
}),
],
});Add the content gating script to your base layout, just before </body>:
<!-- src/layouts/Base.astro -->
<EmDashBodyEnd page={pageCtx} />
<script is:inline src="/_emdash/api/plugins/restrict-with-stripe/assets/rwstripe.js" defer></script>Note: EmDash plugins cannot currently inject scripts into themes automatically. This manual step will be unnecessary once EmDash adds full
page:fragmentssupport for plugin-injected scripts.
Create a page at src/pages/account/verify.astro that handles magic link authentication:
---
const token = Astro.url.searchParams.get("token");
const redirect = Astro.url.searchParams.get("redirect") || "/";
let error = "";
let sessionToken = "";
let redirectUrl = redirect;
if (token) {
try {
const res = await fetch(
`http://127.0.0.1:4321/_emdash/api/plugins/restrict-with-stripe/auth/verify?token=${encodeURIComponent(token)}&redirect=${encodeURIComponent(redirect)}`
);
const json = await res.json();
const data = json.data || json;
if (data.ok && data.sessionToken) {
sessionToken = data.sessionToken;
redirectUrl = data.redirect || redirect;
} else {
error = data.error || "Invalid or expired login link.";
}
} catch {
error = "Verification failed.";
}
} else {
error = "No token provided.";
}
---
<html>
<body>
{error ? (
<p>{error}</p>
) : (
<script is:inline define:vars={{ sessionToken, redirectUrl }}>
document.cookie = `rwstripe_session=${sessionToken}; path=/; max-age=${30*24*60*60}; SameSite=Lax`;
window.location.href = redirectUrl;
</script>
)}
</body>
</html>Adjust the port (4321) to match your dev server.
- Go to your EmDash admin:
/_emdash/admin - Navigate to RWStripe Settings in the sidebar
- Click Connect to Stripe (or check "Connect in Test Mode" first for testing)
- Authorize the connection on Stripe's site
- You'll be redirected back to your EmDash admin with the connection confirmed
- Go to your Stripe Dashboard
- Create products with prices (one-time or recurring)
- Each product needs at least one default price
- Edit any post or page in the EmDash admin
- Click the Restrict button in the toolbar (lock icon, next to Preview)
- Select which Stripe products are required to view the content
- Click Apply
Visitors without access will see a paywall with options to purchase or log in.
- Visitor hits a restricted page
- Content is replaced with a paywall showing "Purchase" and "Log In" tabs
- On the Purchase tab: enter email, select product, click checkout
- Redirected to Stripe Checkout to complete payment
- After payment, Stripe redirects back to the page with a session token
- Session cookie is set automatically — content is now visible
- Visitor hits a restricted page, session cookie has expired
- Click the Log In tab, enter email
- A magic link is sent to their inbox
- Click the link → session cookie set → content visible
- Access is verified against Stripe's API (active subscription or paid invoice)
Every access check queries the Stripe API directly:
- Look up customer by email (
GET /v1/customers?email=...) - Check active/trialing subscriptions for the required products
- Check paid invoices for one-time purchases
- Grant or deny access based on the results
No local state is trusted. The Stripe API is the source of truth.
src/
index.ts # Plugin descriptor (loaded by astro.config.mjs)
plugin.ts # Plugin implementation (hooks, routes, storage)
admin.tsx # React admin UI (settings, restrictions, toolbar button, modal)
stripe.ts # Fetch-based Stripe API client (no SDK dependency)
handlers/
auth.ts # Magic link send + verify
checkout.ts # Stripe Checkout session creation
portal.ts # Stripe Customer Portal session
access.ts # Content access verification
products.ts # List Stripe products
restrictions.ts # CRUD for content restrictions
settings.ts # Plugin settings (Stripe keys, display prefs)
The plugin uses EmDash's plugin storage system (SQLite-backed):
| Collection | Purpose |
|---|---|
restrictions |
Which content requires which Stripe products |
taxonomyRestrictions |
Category/tag-level restrictions |
customers |
Email → Stripe customer ID mapping |
authTokens |
Magic link tokens (15-minute expiry) |
sessions |
Login sessions (30-day expiry) |
| Route | Auth | Purpose |
|---|---|---|
POST /checkout |
Public | Create Stripe Checkout session |
GET /access |
Public | Check if current user can view content |
GET /portal |
Session | Get Stripe Customer Portal URL |
POST /auth/send-link |
Public | Send magic link email |
GET /auth/verify |
Public | Verify magic link token |
GET /auth/logout |
Public | Clear session |
GET /admin/products |
Public | List Stripe products |
GET/POST/DELETE /admin/restrictions |
Admin | Manage restrictions |
GET/POST /admin/settings |
Admin | Plugin settings |
Magic link emails are sent via SMTP. Configure in the plugin's KV store:
| Key | Default | Description |
|---|---|---|
smtp_host |
127.0.0.1 |
SMTP server host |
smtp_port |
1025 |
SMTP server port |
from_email |
noreply@emdash.local |
Sender email address |
site_name |
EmDash Site |
Site name in emails |
For local development, use Mailpit to capture emails.
For production, configure a real SMTP server or update the auth handler to use an email API (Resend, SES, etc.).
- EmDash CMS v0.1.0+
- Node.js runtime (not Cloudflare Workers — uses
node:netfor SMTP) - A Stripe account with products and prices configured
This plugin is a direct port of Restrict With Stripe for WordPress. Key differences:
| Feature | WordPress Version | EmDash Version |
|---|---|---|
| Content restriction | Post meta + term meta | Plugin storage (SQLite) |
| Content filtering | the_content filter |
Client-side JS gating |
| User accounts | WordPress users | Magic link sessions |
| Checkout | WP REST API + Stripe Checkout | Plugin route + Stripe Checkout |
| Admin UI | WP Settings page + meta boxes | React admin pages + toolbar button |
| Stripe SDK | PHP SDK | Fetch-based client (no SDK) |
GPL-2.0-or-later
Built by Stranger Studios, the team behind Paid Memberships Pro.