Higher Quality, Stronger Performance, Increased Stability, Better Developer Experience, discover everything we've shipped recently!

Strapi plugin logo for Draft Preview

Draft Preview

Preview unpublished Strapi content from your frontend by sending one HTTP header. Works on REST and GraphQL.

Strapi Plugin: Draft Preview

CI npm version npm downloads License: MIT

Preview unpublished Strapi content from your frontend with a single HTTP header, securely.

Why you'd want this

By default, Strapi's draft mode:

  • Requires you to manually request the draft status for every query.
  • Is tied to the same find permission 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=draft blocked entirely.

Install

npm install strapi-plugin-draft-preview

Enable 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!

Security

Whether the plugin honours the header is decided by the auth gate, in this order of priority:

  1. authorize: a custom callback. If you set it, it decides.
  2. requireAuth: built-in check. true allows callers authenticated via API token. String forms ("api-token", "admin") pin to one strategy.
  3. NODE_ENV env gate (default): the header is honoured outside production, denied in production. Override via authorize.
Caller?status=draft (native)x-include-drafts header
Allowed by gatedraftsdrafts
Denied by gate, guardNativeStatus: false (default)drafts (Strapi serves them)published
Denied by gate, guardNativeStatus: truepublishedpublished

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.

Use case 2: admin-only previews in production (single shared CMS)

"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.

Use case 3: per-environment isolation

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.

IP allow-listing, geo-fencing, etc.

Express it from authorize:

authorize: (ctx) => allowedIps.includes(ctx.ip),

For richer rules (IP reputation, rate limits, geo) use your CDN or WAF.

Full access

If you genuinely want the header to be public:

authorize: () => true,

Configuration (all optional)

KeyTypeDefaultDescription
headerNamestring"x-include-drafts"HTTP header that flips a request into draft mode.
expectedHeaderValuestring"true"Header value treated as truthy.
statusValuestring"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.
requireAuthtrue \| "api-token" \| "admin" \| falsefalseBuilt-in check. true allows callers authenticated via API token or admin JWT; string forms pin to one strategy.
guardNativeStatusbooleanfalseWhen 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.

Compatibility

  • Strapi 5.x
  • Node 20, 22, 24

Upgrading from v1

Upgrading from v1? See CHANGELOG.md for the migration paths.

Contributing

Contributions welcome. See CONTRIBUTING.md for setup, tests, and the changeset workflow.

Licence

MIT

Install now

npm install strapi-plugin-draft-preview

STATS

No GitHub star yet138 weekly downloads

Last updated

12 days ago

Strapi Version

>=5.0.0 <6.0.0-0

Author

github profile image for Bret Cameron
Bret Cameron

Useful links

Create your own plugin

Check out the available plugin resources that will help you to develop your plugin or provider and get it listed on the marketplace.