How to reference a Sanity account user as an "author" for a document.

9 replies
Last updated: Jan 27, 2022
Hi all! I'm looking to reference a Sanity account user as an "author" for a given document. What's the recommended approach for this? Thanks!
AI Update

Great question! Referencing Sanity account users as authors is a common need. Since Sanity account users aren't stored as documents in your Content Lake (they exist at the project/organization level), you can't create traditional document references to them directly. Here's the recommended approach:

For most content-driven projects, creating a dedicated author document type gives you the most flexibility and is the standard approach:

defineType({
  name: 'author',
  type: 'document',
  fields: [
    defineField({name: 'name', type: 'string'}),
    defineField({name: 'email', type: 'string'}),
    defineField({name: 'userId', type: 'string', title: 'Sanity User ID'}),
    defineField({name: 'bio', type: 'text'}),
    defineField({name: 'photo', type: 'image'}),
    defineField({name: 'socialLinks', type: 'array', of: [{type: 'url'}]})
  ]
})

Then use a standard reference in your content documents:

defineField({
  name: 'author',
  type: 'reference',
  to: [{type: 'author'}]
})

Why this approach works best:

  • Full control over author information and presentation
  • Can add bios, photos, social links, and other content-specific metadata
  • Easy to query with GROQ
  • Authors can be managed through Studio like any other content
  • Works seamlessly with Studio's reference UI
  • Authors become part of your content model, which is useful for public-facing sites

Automatically Capture Current User Info

If you want to automatically populate author information when a document is created, you can use initial value templates. The currentUser object is available in the template context:

// In your sanity.config.ts
export default defineConfig({
  // ... other config
  document: {
    newDocumentOptions: (prev, context) => {
      return [
        ...prev,
        {
          id: 'article-with-author',
          title: 'Article',
          schemaType: 'article',
          value: () => ({
            authorName: context.currentUser?.name,
            authorEmail: context.currentUser?.email,
            authorId: context.currentUser?.id
          })
        }
      ]
    }
  }
})

In your schema, define fields to store this information:

defineField({
  name: 'authorId',
  type: 'string',
  title: 'Author ID',
  readOnly: true
}),
defineField({
  name: 'authorName',
  type: 'string',
  title: 'Author Name',
  readOnly: true
})

Pros: Automatic capture of who created the document, minimal setup
Cons: Values are snapshots at creation time - if a user changes their name later, existing documents won't update automatically; limited to basic user info (name, email, ID)

Hybrid Approach

You can combine both methods by storing the userId in your author documents to maintain a link to Sanity account users while still having rich author profiles:

defineType({
  name: 'author',
  type: 'document',
  fields: [
    defineField({
      name: 'sanityUserId',
      type: 'string',
      title: 'Sanity User ID',
      description: 'Links this author to their Sanity account'
    }),
    // ... other author fields
  ]
})

This gives you the best of both worlds - rich author content plus a connection to actual Sanity users for permissions and workflows.

For most blogs, news sites, or content platforms where authors are displayed publicly, the separate author documents approach (first option) is definitely the way to go!

Thanks, Rachel, so you first need to manually create a person document for each user you want available, correct?
Correct! I'm putting together a script for you. Hold tight!
Thank you πŸ™‚
Thank you πŸ™‚
Ok, I created the following script. You'll need to adjust the
doc
object to match whatever schema you have set up for your author types, but the rest of it should work for you.
import { studioClient } from "./studioClient"
import cq from 'concurrent-queue'
import userStore from 'part:@sanity/base/user'

// Create a queue to limit the rate at which you write changes to Sanity
let queue = cq().limit({ concurrency: 2 }).process(function (task) {
  return new Promise(function (resolve, reject) {
      setTimeout(resolve.bind(undefined, task), 1000)
  })
})

const createUserDocuments = async () => {
  const allUsers = await studioClient
    .fetch(`*[_id in path('_.groups.*')].members[@ != 'everyone']`)
    .then(res => userStore.getUsers(res))
    .then(users => users.filter(user => user.isCurrentUser))

    for (const user of allUsers){
      const doc = {
        _type: 'person',
        _id: user.id,
        name: user.displayName,
        //other fields you have in your schema
      }
      queue(doc).then(async () => {
        //create the doc via the client
        studioClient.create(doc)
          .then(updatedDoc => {
            console.log(`Hurray, the doc is updated! New document:${updatedDoc._id}`)
          })
          .catch((err) => {
            console.error('Oh no, the update failed: ', err.message)
          })
      })
    }
 
}



createUserDocuments()

// execute this script by running 
// $ sanity exec ./path-to-your-script --withUserToken --mock-browser-env
Oh, I have a reusable
studioClient
configured in my Studio that I import in that script. You can just replace it with:
import client from "part:@sanity/base/client"

const studioClient = client.withConfig({apiVersion: '2021-03-25'})
Awesome! Thank you. I'm trying it out now
Thanks again, Racheal. This was just what I needed. I had to remove the line with
.then(users => users.filter(user => user.isCurrentUser))
to have it load in all the existing users instead of just the current user but it worked great. I even modified it to load in the profile images. I appreciate your help on this!
Awesome, glad it worked for you!

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?