How AI is powering better personalization in e-commerce [with Vercel]. Sign up now →

How to authenticate draftMode for users with write access in Next.js 13?

2 repliesLast updated: Nov 30, 2025

Hi 👋 - I've been struggling to authenticate
draftMode
- as in, I only want users with write access to be able to access/see draftMode - I've had a look at https://github.com/sanity-io/preview-kit but I'm still stuck (nextjs 13 app)
Any advice?
AI Update

I totally understand the frustration with authenticating draft mode! The good news is that the @sanity/preview-url-secret package is specifically designed to solve this problem. Let me walk you through how to secure it so only users with write access can enable draft mode.

The Solution: Use @sanity/preview-url-secret

This package creates a secure workflow where:

Here's how to set it up for Next.js 13 App Router:

Step 1: Install the package

npm install @sanity/preview-url-secret @sanity/client

Step 2: Configure the Presentation Tool

In your sanity.config.ts:

import { defineConfig } from 'sanity'
import { presentationTool } from 'sanity/presentation'

export default defineConfig({
  // ... other config
  plugins: [
    presentationTool({
      previewUrl: {
        origin: 'http://localhost:3000', // your Next.js URL
        previewMode: {
          enable: '/api/draft', // the API route we'll create
        },
      },
    }),
  ],
})

Step 3: Create a Read Token

Create a Sanity API token with Viewer rights (not Editor!). Add it to your .env.local:

SANITY_API_READ_TOKEN=your_token_here

Step 4: Create the Draft Mode API Route

Create app/api/draft/route.ts:

import { client } from '@/sanity/lib/client'
import { validatePreviewUrl } from '@sanity/preview-url-secret'
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

const clientWithToken = client.withConfig({
  token: process.env.SANITY_API_READ_TOKEN,
})

export async function GET(req: Request) {
  const { isValid, redirectTo = '/' } = await validatePreviewUrl(
    clientWithToken, 
    req.url
  )
  
  if (!isValid) {
    return new Response('Invalid secret', { status: 401 })
  }

  draftMode().enable()
  redirect(redirectTo)
}

Create app/api/disable-draft/route.ts:

import { draftMode } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'

export function GET(request: NextRequest) {
  draftMode().disable()
  return NextResponse.redirect(new URL('/', request.nextUrl.origin))
}

How the Security Works

The magic is in validatePreviewUrl(). Here's what it does according to the @sanity/preview-url-secret documentation:

So effectively, only Sanity users with write access can generate valid preview URLs.

Customizing Permissions (Enterprise)

If you're on an Enterprise plan and want even more control, you can create custom roles that restrict access to preview secrets. The preview-url-secret reference explains how to:

Debugging

If it's still not working, add the debug plugin to see the secrets being created:

import { debugSecrets } from '@sanity/preview-url-secret/sanity-plugin-debug-secrets'

export default defineConfig({
  plugins: [
    debugSecrets(), // View secrets in Studio
    // ... other plugins
  ],
})

The key insight is that @sanity/preview-url-secret ties authentication to Sanity's built-in permission system rather than requiring you to build your own auth layer. Users without write access simply can't generate the secrets needed to enable draft mode.

For more details on the complete setup, check out the Visual Editing with Next.js App Router guide and the Draft Mode documentation.

Hope this helps! Let me know if you run into any issues with the setup.

Show original thread
2 replies

Was this answer helpful?

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Related contributions