Preview unpublished Strapi content from your frontend with a single HTTP header, securely.
By default, Strapi's draft mode:
- Requires you to manually request the draft
statusfor every query. - Is tied to the same
findpermission as published content. If draft leakage matters, Strapi's built-in system can't help.
This plugin solves both issues.
Common use cases:
- Drafts in staging, published in production.
- Drafts for admin users or specific API tokens, published for everyone else.
- Public draft access via
?status=draftblocked entirely.
npm install strapi-plugin-draft-previewEnable the plugin in config/plugins.js:
module.exports = {
"draft-preview": { enabled: true },
};Then send x-include-drafts: true from your frontend. Apollo Client example:
const draftHeaderLink = setContext((_, { headers }) => ({
headers: {
...headers,
...(process.env.NODE_ENV !== "production" && {
"x-include-drafts": "true",
}),
},
}));Now, outside of production, all queries will return drafts!
Whether the plugin honours the header is decided by the auth gate, in this order of priority:
authorize: a custom callback. If you set it, it decides.requireAuth: built-in check.trueallows callers authenticated via API token. String forms ("api-token","admin") pin to one strategy.- NODE_ENV env gate (default): the header is honoured outside production, denied in production. Override via
authorizeorrequireAuth.
| Caller | ?status=draft (native) |
x-include-drafts header |
|---|---|---|
| Allowed by gate | drafts | drafts |
Denied by gate, guardNativeStatus: false (default) |
drafts (Strapi serves them) | published |
Denied by gate, guardNativeStatus: true |
published | published |
Use case 1: staging only, prod hidden (separate Strapi instances)
If you run separate Strapi instances per environment (one for staging, one for production), this is the default behaviour: ship the plugin, set NODE_ENV=production on the prod instance, and the header is automatically ignored there.
If you run one Strapi instance serving multiple frontends (a shared CMS), use case 2 below is the right pattern instead. The env gate alone won't help: a single CMS in production would deny the header for every frontend, including staging.
"draft-preview": {
enabled: true,
config: { requireAuth: true, guardNativeStatus: true },
},Bake an API token into your preview frontend, send it with Authorization: Bearer <token> plus the preview header. Anyone without the token gets published, including via ?status=draft.
This is also the right shape for one Strapi instance serving multiple frontend environments (prod, UAT, develop, etc.). The token decides who sees drafts, not NODE_ENV.
Issue separate API tokens per environment, allow-list them by name:
authorize: (ctx) =>
["preview-uat", "preview-develop"].includes(
ctx.state.auth?.credentials?.name,
),A leaked token in one environment is recoverable by rotating just that token.
Express it from authorize:
authorize: (ctx) => allowedIps.includes(ctx.ip),For richer rules (IP reputation, rate limits, geo) use your CDN or WAF.
If you genuinely want the header to be public:
authorize: () => true,| Key | Type | Default | Description |
|---|---|---|---|
headerName |
string |
"x-include-drafts" |
HTTP header that flips a request into draft mode. |
expectedHeaderValue |
string |
"true" |
Header value treated as truthy. |
statusValue |
string |
"draft" |
Status string injected into queries when the gate allows. |
authorize |
(ctx) => boolean | Promise<boolean> |
(unset) | Custom predicate. If set, its return value is the gate's decision. Thrown errors are treated as deny. |
requireAuth |
true | "api-token" | "admin" | false |
false |
Built-in check. true allows callers authenticated via API token or admin JWT; string forms pin to one strategy. |
guardNativeStatus |
boolean |
false |
When set, denied requests using the native ?status=draft (REST) or status: DRAFT (GraphQL) paths are rewritten to published. Without this, the native paths bypass the gate. |
- Strapi
5.x - Node
20,22,24
Upgrading from v1? See CHANGELOG.md for the migration paths.
Contributions welcome. See CONTRIBUTING.md for setup, tests, and the changeset workflow.
MIT