
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeNo, Next Auth isn't specifically required for this use case, but you'll need some form of authentication to secure the approval process. The key is using a Sanity API token with write permissions combined with Next.js API routes to safely perform mutations from your frontend.
Here's the approach:
You need to verify that the person approving/rejecting comments is authorized (the Studio owner). You can use Next Auth, Auth0, Clerk, or any authentication system you prefer. The authentication layer just needs to confirm the user's identity before allowing them to trigger the approval action.
1. Store a write token securely
Create a robot token with write permissions in your Sanity project (via npx sanity@latest manage → API tab). Store it in an environment variable:
SANITY_WRITE_TOKEN=your-write-token-here
Never expose write tokens in client-side code - they must stay server-side only.
2. Create a Next.js API route
This route validates the user's authentication and performs the mutation:
// app/api/comments/approve/route.ts
import { createClient } from '@sanity/client'
import { NextResponse } from 'next/server'
const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
token: process.env.SANITY_WRITE_TOKEN, // Write token stays server-side
apiVersion: '2025-01-01',
useCdn: false
})
export async function POST(request: Request) {
// 1. Verify authentication (using your auth system)
const session = await getYourAuthSession(request)
if (!session || !isStudioOwner(session.user)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// 2. Get comment ID and approval status from request
const { commentId, approved } = await request.json()
// 3. Perform the mutation
try {
await client
.patch(commentId)
.set({ approved: approved })
.commit()
return NextResponse.json({ success: true })
} catch (error) {
return NextResponse.json({ error: 'Mutation failed' }, { status: 500 })
}
}3. Call from your frontend
// Frontend component
async function handleApprove(commentId: string) {
const response = await fetch('/api/comments/approve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ commentId, approved: true })
})
if (response.ok) {
// Update UI
}
}Your comment schema should include an approved field:
defineType({
name: 'comment',
type: 'document',
fields: [
defineField({ name: 'text', type: 'text' }),
defineField({ name: 'author', type: 'string' }),
defineField({
name: 'approved',
type: 'boolean',
initialValue: false
})
]
})For a more integrated approach, consider using Sanity Functions - serverless compute that runs within Sanity's infrastructure. This eliminates the need for external API routes and provides better security:
// sanity.blueprint.ts
export default defineBlueprint({
functions: [
defineFunction({
name: 'approveComment',
handler: async ({ commentId, approved }, { getClient }) => {
const client = getClient({ apiVersion: '2025-01-01' })
return await client.patch(commentId).set({ approved }).commit()
}
})
]
})You'd still need authentication on your frontend to determine who can call this function.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store