403 Error: Insufficient Permissions on Boolean Mutation with Editor Token

11 replies
Last updated: Feb 1, 2023
Hi All, I'm trying to a mutation to change a boolean true to false and getting this 403 error in the network tab
{
    "error": {
        "description": "Mutation(s) failed with 1 error(s)",
        "items": [
            {
                "error": {
                    "description": "Insufficient permissions; permission \"update\" required",
                    "permission": "update",
                    "type": "insufficientPermissionsError"
                },
                "index": 0
            }
        ],
        "type": "mutationError"
    }
}
My write token is editor so I'm not what else to try. Any help would be appreciated
AI Update

I feel your pain - this is a tricky issue that catches a lot of folks! The "insufficient permissions" error with an editor token usually isn't about the token role itself, but rather about where and how you're using it.

Here are the most common causes and solutions:

1. CORS Origins Not Configured

If you're making mutations from a browser/frontend application, you need to add your domain to the project's CORS origins list, even with a valid token. Without this, mutations will fail with a 403 error.

Fix: Go to manage.sanity.io, select your project → API → CORS Origins, and add your domain (e.g., http://localhost:3000 for development or your production URL). Make sure to check the "Allow credentials" checkbox for that origin - this is crucial when using authentication tokens.

2. Using Write Tokens in Frontend Code

If you're using a token with editor permissions directly in client-side code, this is a security risk and may be blocked. Write tokens with editor permissions should never be exposed in frontend code.

Fix: Move your mutations to a backend API route, serverless function, or Sanity Function where the token stays secure. Your frontend should call this endpoint instead of mutating directly.

3. Token Doesn't Have the Right Role Assignment

Double-check that your token actually has the editor role assigned to it in your project settings.

Fix: In manage.sanity.io → API → Tokens, verify your token shows "Editor" as the role. If not, create a new robot token with the correct role (robot tokens are recommended for production use).

4. Custom Roles or Access Control Rules

If your project uses custom roles or document-level permissions, the editor role might be restricted from updating certain document types or fields.

Fix: Check your project's role configuration in manage.sanity.io → API → Roles to see if there are any GROQ filters or permission restrictions that might block updates.

Quick Debug Steps

  1. Try making the same mutation from the Vision plugin in your Studio (Tools → Vision) - if it works there but not in your code, it's likely a CORS or token exposure issue
  2. Check the network request headers to confirm your token is being sent correctly as Authorization: Bearer YOUR_TOKEN
  3. Verify you're using a robot token (not a personal token) for production/deployed applications

The most common culprit is #1 (CORS with missing "Allow credentials") or #2 (frontend token usage). Start there and let us know if you're still stuck!

Show original thread
11 replies
This is the function I'm hoping makes the mutation
export async function decQuanity(id: string, token?: string | null): Promise<void> {
  if (projectId) {
    const client = createClient({
      projectId,
      dataset,
      apiVersion,
      useCdn,
      token: token || undefined
    })
    console.log(token)
    await client.patch(id).set({ forSale: false }).commit()
  }
}

Hi User. What’s the context where you’re calling this function?
I'm using it in an onclick event. It's store where a painting is either for sale or not for defined by the boolean forSale. I was hoping that when someone adds the painting to the cart, I could could mutate the boolean to false so some else could not buy the paining.
Thank you. I’m not actually sure that
createClient
of
next-sanity
can take a write token (but I’m happy to be proven wrong). I’ll have to take a closer look tomorrow (unless you get an answer before that).
Thanks, have a good night
You too!
Good Morning, I tried this but still 403
export async function decQuanity(id: string): Promise<void> {
  const mutations = [
    {
      patch: {
        id,
        set: {
          forSale: false
        }
      }
    }
  ]
  if (client) {
    await client.patch(id).set({ mutations }).commit()
  }
}
I have a token on the client = to

export const token = process.env.SANITY_API_WRITE_TOKEN

Ahhhh needed Next_Public_ on the env. Now just to get the patch event lined up with the schema
Hello User, easy peasy:

client.patch(id).set({ forSale: false }).commit()

You are mixing the http API for mutations with the client and thus setting your mutations array as an array called mutations with you patch object as an item. 😉
here is all you need
👉 https://www.sanity.io/docs/js-client#patch-update-a-document
Thank you
user J
, start easy peasy, then complicate your coding life, I like that. I had figured the set but hadn't thought to read the sanity client documentation so that is 🥇.

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.

Was this answer helpful?