Generate slug from multiple fields including nested references in Sanity V3

5 replies
Last updated: Mar 7, 2023
Hi all, I wanted to know if someone can help me with this, I am usin*g V3* in Next.js FrontEnd and I want in my product.js schema to generate my Slug based on attributes, example
*slug* = name+color+material+style.


export default defineType({
  name: 'product',
  title: 'Products',
  type: 'document',
  icon: IconPackage,
  description: 'A product that is available for rent',
  fields: [
    defineField({
      name: 'name',
      title: 'Name',
      type: 'string',
      description: 'The name of the product',
      validation: (Rule) => Rule.required()
    }),
    defineField({
      name: 'description',
      title: 'Description',
      type: 'string',
      description: 'The description of the product'
    }),
    defineField({
      name: 'sku',
      title: 'SKU',
      type: 'string',
      description: 'The SKU of the product'
    }),
    defineField({
      name: 'category',
      title: 'Category',
      type: 'reference',
      to: [{ type: 'category' }],
      description: 'The category of the product',
      validation: (Rule) => Rule.required()
    }),
    defineField({
      name: 'properties',
      title: 'Properties',
      type: 'object',
      description: 'The properties of the product',
      fields: [
        {
          name: 'color',
          type: 'reference',
          description: 'The color of the product',
          to: [{ type: 'color' }]
        },
        {
          name: 'material',
          type: 'reference',
          description: 'The material of the product',
          to: [{ type: 'material' }]
        },
        {
          name: 'style',
          type: 'reference',
          description: 'The style of the product',
          to: [{ type: 'style' }]
        },
        {
          name: 'size',
          description: 'The size of the product',
          type: 'string'
        }
      ]
    }),
   .....
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      description: 'The slug for the product',
      options: {
        source: 'name'
      },
      validation: (Rule) => Rule.required()
    })
  ],
})

AI Update

You can definitely generate a slug from multiple fields in Sanity V3! The source option in the slug field accepts a function that receives the document (doc) and can combine multiple fields, including nested references.

Here's how to modify your slug field to combine name, color, material, and style:

defineField({
  name: 'slug',
  title: 'Slug',
  type: 'slug',
  description: 'The slug for the product',
  options: {
    source: (doc) => {
      // Start with the name
      const parts = [doc.name];
      
      // Add properties if they exist
      if (doc.properties?.color) parts.push(doc.properties.color);
      if (doc.properties?.material) parts.push(doc.properties.material);
      if (doc.properties?.style) parts.push(doc.properties.style);
      
      return parts.filter(Boolean).join(' ');
    }
  },
  validation: (Rule) => Rule.required()
})

However, there's an important caveat: Since color, material, and style are references to other documents, the source function will only receive the reference IDs (like {_ref: 'abc123'}), not the actual document data like the title or name of those referenced documents.

If you need to include the actual values from those referenced documents (like the color name), you have a few options:

Option 1: Use Custom Slugify Function

You can use the slugify option with access to more context through the slugify parameter. The context parameter contains document, parent, parentPath, and getClient:

options: {
  source: 'name',
  slugify: async (input, schemaType, context) => {
    const { doc, getClient } = context;
    const client = getClient({ apiVersion: '2024-01-01' });
    
    // Fetch referenced documents
    const refs = [
      doc.properties?.color?._ref,
      doc.properties?.material?._ref,
      doc.properties?.style?._ref
    ].filter(Boolean);
    
    if (refs.length === 0) return input.toLowerCase().replace(/\s+/g, '-');
    
    const query = `*[_id in $refs]{_id, name}`;
    const refDocs = await client.fetch(query, { refs });
    
    // Build slug from actual names
    const parts = [doc.name];
    refDocs.forEach(refDoc => parts.push(refDoc.name));
    
    return parts.join('-').toLowerCase().replace(/\s+/g, '-');
  }
}

Option 2: Store Values Directly (Simpler)

If you need the slug to include these values, consider storing the actual string values alongside the references:

{
  name: 'colorName',  // Store the actual name for slug generation
  type: 'string',
  hidden: true
}

Option 3: Use a Sanity Function to Update on Publish

You could set up a Sanity Function that triggers on document changes and updates the slug based on referenced document data.

As mentioned in the Sanity documentation, you can definitely use multiple fields as a source by combining them in a function. The tricky part is working with references, which require dereferencing to get the actual values. The slugify option with context.getClient is your best bet for accessing referenced document data during slug generation.

Show original thread
5 replies
Have you tried setting up your source and slugify options ?
Thanks, in the end I tried slugify and I implemented it like this

async function SlugWithNameColorMaterialStyle(input, schemaType, context) {
  const { getClient, parent } = context
  const { name, color, material, style } = parent
  const client = getClient({
    apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION
  })
  const colorName = await client.fetch(`*[_id == $id][0].name`, {
    id: color._ref
  })
  const materialName = await client.fetch(`*[_id == $id][0].name`, {
    id: material._ref
  })
  const styleName = await client.fetch(`*[_id == $id][0].name`, {
    id: style._ref
  })
  return slugify(
    `${name && name}-${colorName && colorName}-${
      materialName && materialName
    }-${styleName && styleName}`,
    {
      lower: true
    }
  )
} 
awesome
user S
😊
May I suggest some improvements to your code?
So instead of triple
await
calls, you could improve with something like below:

const [colorName, materialName, styleName] = await Promise.all([
    client.fetch(`*[_id == $id][0].name`, {
      id: color._ref
    }),
    client.fetch(`*[_id == $id][0].name`, {
      id: material._ref
    }),
    client.fetch(`*[_id == $id][0].name`, {
      id: style._ref
    })
  ])

That’ll allow things to run in parallel. I hope it helps! 😊

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?