5.1 KiB
| title | aliases | tags | sources | created | updated | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Stripe Plugin |
|
|
|
2026-05-15 | 2026-05-15 |
Stripe Plugin
Integrates Stripe billing into Payload CMS — two-way sync, webhook handling, and a proxied Stripe REST API behind Payload access control.
Installation
pnpm add @payloadcms/plugin-stripe
Basic Config
import { stripePlugin } from '@payloadcms/plugin-stripe'
buildConfig({
plugins: [
stripePlugin({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
}),
],
})
Options
| Option | Type | Default | Description |
|---|---|---|---|
stripeSecretKey * |
string | — | Stripe secret key |
stripeWebhooksEndpointSecret |
string | — | Webhook signing secret |
rest |
boolean | false |
Open /api/stripe/rest proxy (dev only) |
webhooks |
object | function | — | Webhook event handlers |
sync |
array | — | Auto-sync configs between collections and Stripe resources |
logs |
boolean | false |
Log sync events to console |
Auto-Opened Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/stripe/rest |
POST | Proxied Stripe REST API (behind Payload access control) |
/api/stripe/webhooks |
POST | Stripe webhook receiver |
Webhooks Setup
Dev:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhooks
# paste the secret into .env as STRIPE_WEBHOOKS_ENDPOINT_SECRET
Production: Create webhook in Stripe dashboard → set URL to YOUR_DOMAIN/api/stripe/webhooks → paste secret into .env.
Handler config:
stripePlugin({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
webhooks: {
'customer.subscription.updated': ({ event, stripe, stripeConfig }) => {
// handle event
},
},
})
Serverless Warning
Stripe expects a 2xx response within 10–20 seconds. The plugin processes webhooks asynchronously — in serverless environments the function instance may close before processing completes, causing duplicate events.
Vercel fix: install @vercel/functions — the plugin auto-detects it and wraps handlers in waitUntil().
Sync — Two-Way Auto-Sync
Maps Payload collection fields to Stripe resource properties. Hooks + webhook handlers are created automatically.
stripePlugin({
sync: [
{
collection: 'customers',
stripeResourceType: 'customers',
stripeResourceTypeSingular: 'customer',
fields: [
{ fieldPath: 'name', stripeProperty: 'name' },
],
},
],
})
What sync does automatically:
- Adds read-only
stripeIDfield (Stripe-generated cross-reference) - Adds direct link to resource on Stripe.com in admin
- Adds read-only
skipSyncflag to prevent infinite loops - Attaches hooks:
beforeValidate: createNewInStripe,beforeChange: syncExistingWithStripe,afterDelete: deleteFromStripe - Handles webhooks:
STRIPE_TYPE.created/updated/deleted
Limitation: Only top-level fields supported (Stripe API constraint).
Server-Side Usage
Prefer direct Stripe SDK on server rather than the REST proxy:
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2022-08-01' })
const customer = await stripe.customers.create({ email: data.email })
Or use stripeProxy from the plugin:
import { stripeProxy } from '@payloadcms/plugin-stripe'
const customer = await stripeProxy({
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeMethod: 'customers.create',
stripeArgs: [{ email: data.email }],
})
TypeScript
import { StripeConfig, StripeWebhookHandler, StripeProxy } from '@payloadcms/plugin-stripe/types'
Key Takeaways
- Hides Stripe credentials — client-side calls go through Payload's access control, never directly to Stripe
- REST proxy (
rest: true) is dev-only — opening it in production gives authenticated users full Stripe API access - Two-way sync requires webhooks — set
stripeWebhooksEndpointSecretor changes from Stripe won't propagate to Payload - Serverless + webhooks =
@vercel/functions— without it, async webhook handlers may be killed before completing synconly works on top-level Stripe fields — nested fields require manual hooksskipSyncflag prevents infinite loops — sync hooks set it before triggering webhooks, webhooks check it before re-syncing- For e-commerce, pair with wiki/payloadcms/ecommerce or wiki/payloadcms/ecommerce-payment-adapters
Related
- wiki/payloadcms/plugins
- wiki/payloadcms/ecommerce
- wiki/payloadcms/ecommerce-payment-adapters
- wiki/payloadcms/access-control
- wiki/payloadcms/hooks-collections
Sources
raw/plugins__stripe.md- https://payloadcms.com/docs/plugins/stripe