✨Discover storytelling in the AI age with Pixar's Matthew Luhn at Sanity Connect, May 8th—register now
Last updated December 07, 2022

How to allow users to upload files to Sanity Studio and link them in the content editor

By Stack Five

In this article, we explain how to integrate a media manager into Sanity Studio so that your users can upload and link files in the PortableText content editor.

Our first attempt allowing uploaded files to be linked in the PortableText content editor

A brief look through Sanity's schema types initiated the development process. Sanity contains a schema type called 'Reference.' This schema type allows one to declare a field that points to another piece of data within the CMS, allowing you to access that referenced piece of data outside its original purpose. As this was a failed attempt, there is no need to go through the entire process, but the thought process for implementation was as follows:

  1. Create a new field in the schema declaration in preparation for Portable Text (an 'Array' type made up of the 'Block' type)
  2. Add a custom annotation for an 'Object' type containing a field of type 'Reference.'
  3. Use the object above to attach previously/newly uploaded file references to highlighted pieces of text and interpret the data in the front end.

The approach was simple, and the implementation was very straightforward, but a Sanity limitation overlooked during the process rendered the entire process ineffective. The 'Reference' type in Sanity can only reference other documents, not assets such as the 'Image' or 'File' schema type.

Our second (successful) attempt

Throughout the implementation of the first approach, something became quite clear; it makes sense to be unable to reference Sanity assets. Why is that the case? There is no need to create a reference to an asset when we can just access Sanity assets directly.

The new approach then becomes as follows:

  1. Create a new field in the schema declaration in preparation for Portable Text (an 'Array' type made up of the 'Block' type)
  2. Add a custom annotation for an 'Object' type containing a field of type 'File' instead of type 'Reference' as was in the first approach.
  3. Use the object above to attach previously/newly uploaded file references to highlighted pieces of text and interpret the data in the front end.

The above is a simple modification that demanded only a few code changes. You can check out the code snippets below, along with commented explanations for each step of the coding process.

1) Sanity schema declaration

// File name: articles.js

export default {
    name: 'articles',
    type: 'document',
    title: "News Articles",
    fields: [
          name: 'title',
          type: 'string',
          title: 'Article Title',
          validation: (Rule) => Rule.required()
          name: 'slug',
          type: 'slug',
          description: "Click 'Generate' button to generate slug based off article title. URL path for the article.",
          title: 'Slug',
          options: {
              source: "title",
              slugify: (input) => {
                // Custom slugify function to properly format the slug as a URL path.
                const newSlug = input.toLowerCase().replace(/\s+/g, '-');
                return `/${newSlug}`;
          validation: (Rule) => Rule.required()
          name: 'mainText',
          type: 'array',
          title: "Main Text",
          of: [{ 
              type: 'block' ,
              // The marks declaration below adds an additional option to the Sanity text editor. 
              // This option will allow us to attach files to highlighted pieces of text.
              marks: {
                annotations: [{
                  // This can be the name of your choice but make note of it 
                  // as you will reference it when writing your front end code
                  name: 'assetReference',
                  type: 'object',
                  title: 'File Reference',
                  description: 'Link pieces of text to a previously uploaded file.',
                  fields: [{ 
                      name: 'file', 
                      // Notice the the type here is 'file'.
                      // In the unsuccessful approach, it was originally 'reference'
                      type: 'file', 
                      title: 'File Attachment'

*Note - Don't forget to import the new schema into your schema.js file to have it included in your Sanity studio.

The new schema declaration should give you a text editor in Sanity with an added option for file attachment (see GIF below).

File attachment in action. Pay special attention to the 'File Reference' option in the text field top bar, available thanks to our schema declaration.

2) Interpreting Sanity data in the front end
Once the document is published, it's time to interpret the Sanity data in the front end. We begin by declaring a PortableText element and passing to it a value (our article's text editor content) and a component, the HTML element mapping for each text editor feature (paragraph, h1, h2, etc.). The snippet below has been simplified to display only the most essential code.

import { PortableText } from '@portabletext/react';

// Sanity to HTML Element mapping for Portable Text.
const components = {
  block: {
    normal: ({ children }) => <p>{children}</p>,
    h2: ({ children }) => <h2>{children}</h2>,
  marks: {
    em: ({ children }) => <em>{children}</em>,
    strong: ({ children }) => <strong>{children}</strong>,
    // Remember the name you provided in your schema for the file reference?
    // You should use that same name here to declare your HTML element mapping for it.
    assetReference: ({ children, value }) => {
      // The file reference in the asset object has the form <_file>-<id>-<extension>
      // We split the text string to get the individual pieces of information.
      const [_file, id, extension] = value.file.asset._ref.split('-');
      // The URL to access your file should be of the form
      // https://cdn.sanity.io/files/{PROJECT_ID}/{DATASET}/{FILE ID}.{FILE EXTENSION}
      return (
        <a href={`https://cdn.sanity.io/files/${process.env.NEXT_PUBLIC_SANITY_PROJECT_ID}/${process.env.NEXT_PUBLIC_SANITY_DATASET}/${id}.${extension}?dl=${id}.${extension}`}>

export default function Article({ data }) {

  return (
    <div style={{
        minHeight: '100vh'
      {/* data (in our case) is passed through getServerSideProps 
        and contains a Sanity object with the 'article' schema information. */}
      <PortableText value={data.article.mainText} components={components} />

The 'File' type, much like Sanity's 'Image' type, does not return a direct URL for the assets, it instead returns an asset object containing information about the file which we want. It is then up to us to build the appropriate URL (as seen above).

Notice too that the URL we are building contains a query parameter '?dl={File Id}.{File extension}'. This is taken directly from the Sanity docs. This parameter indicates that we would like to directly download the file. If this isn't the case for you and you would like to open your PDF in the browser, you can omit this query parameter.

This gives us everything we need to make our files accessible to our users. See the final result below.

Final result of adding file attachment option to Sanity text field and interpreting the Sanity data in the front end.


Linking files to text in Sanity is a simple 2-step process. The first requirement is to declare, in your document schema, a text editor in preparation for Portable Text (Sanity Docs) with a custom annotation for file attachment. The second requirement is to fetch your Sanity document in the front end, render a Portable Text element and pass to it your text editor content as well as the HTML element mapping for each text editor feature (including your custom annotation). All files will be available for download in the front end through the following URL: https://cdn.sanity.io/files/{SANITY_PROJECT_ID}/{SANITY_DATASET}/{FILE_ID}.{FILE_EXTENSION}?dl={FILE_ID}.{FILE_EXTENSION}

Sanity – build remarkable experiences at scale

Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.

Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.

Other guides by author