npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strapi-plugin-draft-preview

v2.0.0

Published

Preview unpublished Strapi v5 content from your frontend by sending one HTTP header. Works on both REST and GraphQL with all populated relations, no per-query rewrites needed.

Readme

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

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)

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

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