Convert comma-separated string to multiple tag references in Sanity array

12 replies
Last updated: Dec 2, 2025
Hello, I’m new to Sanity, I’ve created a Tag type that includes a slug and a title, then in the Post type I’ve added this field:

{
      name: 'tags',
      title: 'Tags',
      type: 'array',
      of: [{type: 'reference', to: {type: 'tag'}}]
    }
It works but I have to add / select tags one by one, is there any way to convert a comma-separated string to multiple tags (they are objects and it’s not a string array).

Thanks
AI Update

Great question! Yes, you can definitely create a better UX for adding multiple tags at once. You'll need to build a custom input component that accepts comma-separated input and converts it to tag references.

Here's a working approach:

import {set, unset, insert, setIfMissing} from 'sanity'
import {Stack, TextInput, Button, Card, Text, Flex} from '@sanity/ui'
import {useCallback, useState} from 'react'
import {useClient} from 'sanity'

function TagsInput(props) {
  const {value = [], onChange, schemaType} = props
  const [inputValue, setInputValue] = useState('')
  const client = useClient({apiVersion: '2023-01-01'})
  
  const handleAddTags = useCallback(async () => {
    // Split by comma and clean up
    const tagNames = inputValue
      .split(',')
      .map(t => t.trim())
      .filter(Boolean)
    
    if (tagNames.length === 0) return
    
    // Create or fetch tag documents
    const tagRefs = await Promise.all(
      tagNames.map(async (tagName) => {
        const slug = tagName.toLowerCase().replace(/\s+/g, '-')
        
        // Check if tag already exists
        const existingTag = await client.fetch(
          `*[_type == "tag" && slug.current == $slug][0]`,
          {slug}
        )
        
        if (existingTag) {
          return {
            _type: 'reference',
            _ref: existingTag._id,
            _key: `tag-${Date.now()}-${Math.random()}`
          }
        }
        
        // Create new tag document
        const newTag = await client.create({
          _type: 'tag',
          title: tagName,
          slug: {_type: 'slug', current: slug}
        })
        
        return {
          _type: 'reference',
          _ref: newTag._id,
          _key: `tag-${Date.now()}-${Math.random()}`
        }
      })
    )
    
    // Merge with existing tags
    const newValue = [...(value || []), ...tagRefs]
    onChange(set(newValue))
    setInputValue('')
  }, [inputValue, value, onChange, client])
  
  return (
    <Stack space={3}>
      <Flex gap={2}>
        <Card flex={1}>
          <TextInput
            value={inputValue}
            onChange={(e) => setInputValue(e.currentTarget.value)}
            placeholder="Enter tags separated by commas"
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                e.preventDefault()
                handleAddTags()
              }
            }}
          />
        </Card>
        <Button onClick={handleAddTags} text="Add Tags" tone="primary" />
      </Flex>
      
      {value && value.length > 0 && (
        <Text size={1} muted>
          {value.length} tag{value.length !== 1 ? 's' : ''} selected
        </Text>
      )}
      
      {/* Render the default array input below */}
      {props.renderDefault(props)}
    </Stack>
  )
}

// In your schema:
export default {
  name: 'post',
  type: 'document',
  fields: [
    {
      name: 'tags',
      title: 'Tags',
      type: 'array',
      of: [{type: 'reference', to: {type: 'tag'}}],
      components: {
        input: TagsInput
      }
    }
  ]
}

How this works:

  1. The custom input provides a text field where you can enter comma-separated tag names
  2. When you click "Add Tags" (or press Enter), it splits the string and processes each tag
  3. For each tag name, it checks if a tag document with that slug already exists
  4. If it exists, it creates a reference to it; if not, it creates a new tag document first
  5. All the new references are added to your existing array using the set() patch operation
  6. The renderDefault(props) call at the end shows the standard array input below your custom input, so you can still manage tags individually if needed

Key points:

  • Uses useClient() to access Sanity's client for creating/fetching tag documents
  • Generates unique _key values for each reference (required for array items)
  • Automatically creates slugs from tag titles
  • Maintains your existing reference-based structure
  • You can remove the renderDefault() line if you want only the comma-separated input

This gives you the best of both worlds - quick bulk tag entry while keeping your clean reference-based data structure intact!

Show original thread
12 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.

Was this answer helpful?