Issue with uploading images to Sanity in a Next.js app and seeking help from the community.

43 replies
Last updated: Sep 27, 2022
Hey all, Ive set up an editor token for my Nextjs app but I'm getting
Upload failed: the mutation(s) failed: Insufficient permissions; permission "create" required
when trying to upload a file to
client.assets
AI Update

This is a common permission issue! When you create an Editor token in Sanity, it might not have the specific permissions needed for asset operations. Here's what's happening and how to fix it:

The Problem

The error Insufficient permissions; permission "create" required when using client.assets.upload() means your token doesn't have the necessary permissions to create assets in your project, even though it's labeled as an "Editor" token.

The Solution

You need to ensure your token has a role with asset creation permissions:

  1. Go to Sanity Manage - Access it via your Studio menu or run npx sanity@latest manage in your terminal

  2. Navigate to the API tab and find your token (or create a new one)

  3. Assign a role with asset permissions - The key is to use a role that explicitly includes asset operations. The standard "Editor" role should work, but if you're using custom roles, make sure they include:

    • sanity.assets.create permission
    • sanity.assets.upload permission
  4. Verify you're using a Robot Token (not a Personal Token) for production use, as robot tokens can be assigned specific roles independent of individual users

Check Your Client Configuration

Make sure you're passing the token correctly in your Next.js app:

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'your-dataset',
  token: process.env.SANITY_API_TOKEN, // Make sure this is set
  useCdn: false, // Must be false for write operations
  apiVersion: '2024-01-01'
})

Important: Never expose write tokens in client-side code! If you're uploading from the browser, implement a serverless function or API route that handles the upload server-side where the token remains secure.

Common Gotchas

  • Custom roles: If your project uses custom roles, verify they include asset permissions in the role definition
  • Dataset restrictions: Some roles might be restricted to specific datasets - ensure your token's role has access to the dataset you're uploading to
  • Token regeneration: If you just created/modified the token, try regenerating it or creating a fresh one
  • useCdn must be false: Asset uploads require useCdn: false in your client configuration

If you're still having issues after checking the role permissions, try creating a completely new robot token with explicit Editor or Administrator permissions to rule out any token-specific issues. You can verify the token's permissions by checking which role it's assigned in the Sanity Manage interface.

It sounds like your app may not be picking up on your token. How have you set it up?
all im trying to do is this exact functionality of this todo app but with images as well
id literally pay some1 help me add this functionality lmao but im a dev so I wanna figure it myself... im close I think ? but its not super simple to add images to Sanity yet, harder than I thought.
basically just want to plug in a little file field and grab the image and add it to sanity along with the title, date, etc. in this todo app that's already made (essentially). Thanks for ANY advice you can offer!
I forked this repo to start, so I have this same repo, btw
i've set up the token and connect to the api and sending strings just fine this image is causing issues tho
sorry - to clarify if I try to upload the image from the page under
pages/todo.js
like using the front-end, then I get the permissions error, and if I try to send it to the
api/todo.js
api call then the
File
object turns into an empty object
{}
sorry to bother w such a trivial thing but im stuck 😕
I got ALL strings to publish to the API just fine, its just files that throw me this permissions error
user M
I am so sorry to bother you even a little -- really hope you could maybe help me identify whats up with the permissions because I might be able to take it from there myself heh
I said alot above but, simply put, I do have my token set up and I am able to publish strings but
client.assets
seems to have permissions issues, is there more I need to do for CORS or Token settings?
Can you share your specific code? It's difficult to know what's wrong without it.
its this repo exactly with my sanity credentials and my sanity token: https://github.com/bathrobe/next-magic-sanity-todo
I don't see where that's attempting to upload assets.
okay ill need to submit my code up to github 1 moment
sorry wish this was a simpler thing 1 moment plz
user M
here it is thank u, link below to where the file input field is
I know this is alot sorry 😕 - so basically where do I need to be uploading the image? The
page
itself or the
/api/todo
call?
Because here im calling it on the page: https://github.com/outer-code/example-4-sanity/blob/5714fdc4f797df0c52d4815d390e0d969244a210/src/pages/todos.js#L74 -- maybe thats the permissions issue. but if I try to send this
File
to the api it disappears because of the stringify.... any idea how I send the image to the API along with the strings?
such a huge task simply to upload an image from the front-end... strings were nothing but this is v time-consuming for me so far
My original question in help today was "Anyone have easy examples of uploading images to sanity from Nextjs" and I still have not found a single easy example of this and I can't guess how to do it myself
user M
would you like me to explain again maybe clarify more what my issue is?
scratch this thread the code I submitted wont explain it - we can close out completely thanks for ur time
Regarding your most recent post in the help channel, I'm sorry you feel like support isn't happening promptly enough. I have been trying to help you to the best of my ability, but you shared your code about 20 minutes ago. Like I said, it's tough to diagnose what's wrong without seeing someone's actual code.
We get several hundred requests for help from the community each week, so unfortunately that does mean it can take a bit for us to have a chance to circle back to a thread during the day.

I do understand what your issue is, but I can't pinpoint the exact problem with your code without reading and replicating it (which again, can take some time).
i'm sorry - my own project is my issue not u - I just need to know how, when submitting these strings and stuff, I can also send an image file to sanity. i'm using the repo example. my most recent little git I shared w u doesn't even run it's not worth checking. ill organize it better and let ya know here.
Please don't take that as me saying I do not want to help you! It just means as much as I want to immediately resolve a problem, I'm unfortunately a bit limited. I am working on putting together a straightforward upload example for you!
are you serious? omg that would be insanely helpful...
problem is I have
/pages/todo.js
which has my form and which sends my strings... I dont know how to send the image file to the
/api/todo.js
call... and the in that call I dont know how to handle the file exactly.
So my problem is if I am uploading the image on
/pages/todo.js
but I cannot stringify it... how do I send it to the
POST
I am making with all the other stuff? Like how do I include the image file with the stringified content is my issue basically. (so sorry to rant trying to be detailed)
user M
if you look at this line: https://github.com/outer-code/example-4-sanity/blob/6be196b85adb5cb3b0efd426c94d2595df2dfea9/src/pages/todos.js#L60
You can see where I try to upload the image and this is where I get a permissions problem.
omg smacks head dude I think this was just because I didnt have NEXT_PUBLIC_ on that damn env variable hahah (sorry to swear) about to test now...
btw im texan we call every1 "dude".... ill lyk if this fixes it.
user M
think that did it... wow lol. It was an .env variable 😬😬
Ok, so to your question about how to upload from a form in Next to Sanity. First off, I configure my client like this:
export const client = sanityClient({
  projectId: 'k8p6uw8a',
  dataset: 'development',
  apiVersion: '2021-03-25', // use current UTC date - see "specifying API version"!
  token: process.env.NEXT_PUBLIC_SANITY_WRITE_TOKEN,
});
For the token to be picked up by the browser, I have to add
NEXT_PUBLIC
to the beginning of my env. Then the component looks like this:
import React, { useState } from 'react';

import { client } from '../src/sanity';

export default function Todos({ todos }) {
  const [todoList, setTodoList] = useState(todos);
  const [userInput, setUserInput] = useState({
    text: '',
    image: '',
  });

  const handleSubmit = async e => {
    e.preventDefault();
    const { _id } = await client.assets.upload('image', userInput.image);

    const newTodo = {
      _type: 'todo',
      text: userInput.text,
      image: {
        _type: 'image',
        asset: {
          _type: 'reference',
          _ref: _id,
        },
      },
    };

    const todo = await client.create(newTodo);
    console.log(todo)
  };

  const handleChange = e => {
    e.preventDefault();
    const value = e.target.type == 'text' ? e.target.value : e.target.files[0];
    setUserInput({ ...userInput, [e.target.name]: value });
  };

  return (
    <div className='max-w-4xl mx-auto '>
      <main className='text-center'>
        <div className='my-8'>
          <h1 className='text-4xl font-bold tracking-tight '>My To-do List</h1>
        </div>
        <form>
          <div className='flex justify-center items-center'>
            <input
              name='text'
              className='w-72 h-12 border p-4 border-blue-100'
              type='text'
              value={userInput.text}
              placeholder='Make coffee.'
              // our function
              onChange={handleChange}
            />
            <div className='my-8'>
              <input type='file' onChange={handleChange} name='image' />
            </div>
          </div>{' '}
          <button
            className='focus:outline-none focus:ring focus:border-blue-800
						px-6 py-2 rounded-xl bg-blue-500 text-blue-50 hover:bg-blue-800 
						font-semibold'
            onClick={handleSubmit}
          >
            Add to do
          </button>
        </form>
        <div className='my-12'>
          <h1
            className='text-xl font-bold tracking-tight 
					my-8'
          >
            Your Todos
          </h1>
          <ul>
            {todoList &&
              todoList.map(todo => <li key={todo._id}>{todo.text}</li>)}
          </ul>
        </div>
      </main>
    </div>
  );
}

export const getServerSideProps = async () => {
  const todos = await client.fetch(`*[_type == 'todo']`);

  return {
    props: {
      todos,
    },
  };
};
This isn't the most secure way to do this though, because your token ends up showing up in the network tab. So, we can use Next API routes instead. Note, I don't need the
NEXT_PUBLIC
prefix on my env var:
/api/uploadForm.js

import { client } from '../../src/sanity';
import multer from 'multer';

async function parseFormData(req, res) {
  const storage = multer.memoryStorage();
  const multerUpload = multer({ storage });
  const multerFiles = multerUpload.any();
  await new Promise((resolve, reject) => {
    multerFiles(req, res, result => {
      if (result) {
        return reject(result);
      }
      return resolve(result);
    });
  });
  return {
    fields: req.body,
    files: req.files,
  };
}

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req, res) {
  const sanityClient = client.withConfig({
    token: process.env.SANITY_WRITE_TOKEN,
  });

  const data = await parseFormData(req, res);

  const { _id } = await sanityClient.assets.upload(
    'image',
    data.files[0].buffer
  );
  const newTodo = {
    _type: 'todo',
    text: data.fields.text,
    image: {
      _type: 'image',
      asset: {
        _type: 'reference',
        _ref: _id,
      },
    },
  };

  const todo = await sanityClient.create(newTodo);

  res.redirect('/');
}
In that second example, the form would look like this:
<form
          action='/api/uploadForm'
          method='POST'
          encType='multipart/form-data'
        >
          <div className='flex justify-center items-center'>
            <input
              name='text'
              className='w-72 h-12 border p-4 border-blue-100'
              type='text'
              value={userInput.text}
              placeholder='Make coffee.'
              // our function
              onChange={handleChange}
            />
            <div className='my-8'>
              <input type='file' onChange={handleChange} name='image' />
            </div>
          </div>{' '}
          <button
            className='focus:outline-none focus:ring focus:border-blue-800
						px-6 py-2 rounded-xl bg-blue-500 text-blue-50 hover:bg-blue-800 
						font-semibold'
            // onClick={handleSubmit}
          >
            Add to do
          </button>
        </form>

But I see that you got it sorted out now! I hope this explanation helps if you hit any other stumbling blocks!
VERY COOL - I will need to use elements from this to match some needs for our app next up thank you!!
saved these - this was so helpful
My method is very insecure compared to either of these so ill be refactoring w ur kindly-written code here thanks again
Awesome! Glad you got it working!

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?