@sanity/preview-url-secret

Generates and validates signed, expiring secrets that gate draft-mode preview access for Sanity Studio-driven front ends.

Library
npm
v4.1.5
63stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
80/100Excellent
Development Activity96
Maintenance100
Community52
Maturity52
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture80
Code Quality78
Innovation68
Learning Curve75

@sanity/preview-url-secret is the package behind Sanity’s Presentation Tool preview-link flow: it mints a short-lived, cryptographically random secret tied to a draft document, encodes it into a preview URL, and validates that secret server-side before an application is allowed to enter draft/preview mode. It ships as a set of small, tree-shakeable entry points — validatePreviewUrl for verifying an incoming request, createPreviewSecret for minting a new secret, togglePreviewAccessSharing for the Studio’s shareable-preview-link feature, and helpers for stripping preview query params before rendering — rather than one monolithic API.

The package is deliberately client-agnostic: SanityClientLike is a narrow structural type compatible with both @sanity/client’s SanityClient and the client type re-exported from the sanity package, so the same validation logic works whether it’s called from a Next.js Route Handler, a Remix loader, or a Cloudflare Worker. It handles edge-runtime quirks (a deliberate delay for Cloudflare Workers, disabling cache: 'no-store' where unsupported), GROQ queries scoped to a dedicated sanity.previewUrlSecret document type with automatic expiry-based garbage collection, and forwarding of related state (preview perspective, editing variant, Vercel Protection Bypass tokens) through the redirect chain.

It is consumed almost exclusively as a transitive dependency of @sanity/presentation, next-sanity, and similar framework-loader packages in the same sanity-io/visual-editing monorepo, rather than installed and used directly by most application code — but any team building a custom preview/draft-mode integration against Sanity’s Presentation Tool needs its validatePreviewUrl and definePreviewUrl primitives directly.

What You Get

  • validatePreviewUrl() — parses an incoming request URL, validates its embedded secret against Sanity via GROQ, and returns whether the preview is valid plus the redirect path and Studio origin
  • createPreviewSecret() / secret-sharing toggles — server-side primitives for minting per-document secrets and for enabling/disabling a long-lived shared preview link
  • definePreviewUrl() — builds a PreviewUrlResolver that composes the final enable/preview URL, handling recursion guards and legacy draftMode option compatibility
  • URL query-param helpers (withoutSecretSearchParams, setSecretSearchParams, getRedirectTo) for cleanly stripping or injecting preview-related params before rendering a page
  • Automatic forwarding of Vercel Deployment Protection Bypass tokens through the redirect chain when previewing protected Vercel deployments
  • A SanityClientLike structural type so the package works with either @sanity/client’s or sanity’s client typings without a hard dependency on either

Common Use Cases

  • Implementing a Next.js App Router /api/draft route that validates the secret and calls draftMode().enable() before redirecting into the previewed page
  • Implementing the equivalent Pages Router or Remix.js resource-route handler for entering draft mode via session cookies instead of Next’s built-in draft mode
  • Enabling Sanity’s shareable-preview-link feature so a stakeholder without a Studio login can view drafts via a URL containing a long-lived secret
  • Debugging which preview secrets exist in a dataset via the companion @sanity/debug-preview-url-secret-plugin Studio plugin
  • Building a custom framework integration (outside Next.js/Remix) that still needs to safely validate a Presentation Tool preview request

Under The Hood

Architecture The package is organized as a flat set of single-purpose modules under src/ rather than one monolithic client: validatePreviewUrl.ts orchestrates the read path (parse URL via parsePreviewUrl.ts, reconfigure the caller’s client via createClientWithConfig.ts, check the secret via validateSecret.ts), while createPreviewSecret.ts and togglePreviewAccessSharing.ts own the write paths for per-document and shared-access secrets respectively. Each public capability is also exposed as its own exports subpath (./create-secret, ./define-preview-url, ./toggle-preview-access-sharing, etc.) backed by thin re-export files in src/_exports/, so bundlers can tree-shake unused code paths — a Next.js app using only validatePreviewUrl never pulls in the secret-creation or Vercel-bypass-subscription code. State never lives in the package itself; every secret is persisted as a Sanity document (sanity.previewUrlSecret / sanity.previewUrlShareAccess) queried via scoped GROQ constants in constants.ts, making the package a stateless orchestration layer over the Studio’s own dataset.

Tech Stack Written in strict TypeScript (@sanity/tsconfig/strictest) and built with @sanity/pkg-utils, targeting dual ESM output per subpath export with generated .d.ts files. Its only runtime dependency is @sanity/uuid for document ID generation; @sanity/client is a peer dependency (^7.26.2 || ^8.0.0) rather than a hard dependency, letting consumers bring their own client version. It uses platform Web APIs directly (WebCrypto’s crypto.getRandomValues, the URL/URLSearchParams globals, btoa) instead of Node-specific APIs, which is what makes it portable to edge runtimes like Cloudflare Workers and Vercel Edge Functions. The monorepo builds all packages with Turborepo and publishes via Changesets.

Code Quality Tests run under Vitest with vitest --pass-with-no-tests --typecheck, and the test suite that exists (parsePreviewUrl.test.ts) is thorough for the URL-parsing surface — covering absolute/relative URLs, hash preservation, perspective/variant forwarding, and Vercel bypass-token forwarding — but the secret-creation and Studio-write-path modules (createPreviewSecret.ts, togglePreviewAccessSharing.ts) have no direct unit tests, relying instead on integration coverage from consuming packages in the monorepo. Error handling favors typed returns ({isValid: false}) over thrown exceptions on the read path, with console.error logging gated behind a NODE_ENV === 'development' check. The codebase is linted with oxlint (a Rust-based ESLint alternative) and formatted with oxfmt, both run in CI alongside a strict TypeScript build.

API Design The public surface is deliberately narrow: most exports are marked @internal or @alpha in TSDoc comments, with only validatePreviewUrl, SanityClientLike, and PreviewUrlValidateUrlResult marked @public, signaling that direct consumption is expected mostly through the small set of documented entry points rather than the full module tree. The SanityClientLike structural type is a notable ergonomics choice — it lets the package accept either @sanity/client’s or the sanity package’s client type without either being an actual dependency, avoiding a common monorepo version-mismatch failure mode.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search