Approve Sanity Comments as Studio Owner from Next.js Front-end
No, 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:
The Authentication Strategy
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.
Implementation Pattern
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
}
}Schema Setup
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
})
]
})Alternative: Sanity Functions
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.
Summary
- Next Auth works, but any auth system will do - you just need to verify identity
- The critical part is using a write token in a secure server-side context (API route or Sanity Function)
- Never expose write tokens in frontend code
- Your auth system controls who can approve; the API token controls what operations are allowed
Show original thread6 replies
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.