How to slugify or transform string values inside GROQ queries?

6 replies
Last updated: Nov 30, 2025
Hey all, is it possible to transform the output of a string (inside GROQ)? I’m building a nav schema which is an array of parent, child references. Parent references are optional and I’m falling back to the title specified as the base “slug” for children.
Trying to achieve something like this…


  *[_type == "navigation"] {
    items[] {
      // Can I "slugify" the "title"?
      "slug": coalesce(page->slug.current, title),
      children[] {
        "slug": page->slug.current,
      }
    }
  }
Happy to “slugify” the value returned for slug but was just wondering about the possibility of doing this inside GROQ itself
AI Update

Yes, GROQ has string manipulation functions that can help you transform strings, though there isn't a built-in slugify() function. You can achieve some string transformations using the available string namespace functions.

Basic string transformations in GROQ

GROQ provides several string functions in the string:: namespace:

  • lower(string) and upper(string) - convert to lowercase/uppercase
  • string::split(string, separator) - splits a string into an array
  • array::join(array, separator) - joins array elements into a string
  • string::startsWith(string, prefix) - checks if string starts with a prefix

Creating a slug-like transformation

While there's no native slugify() function, you can combine these functions for basic transformations:

*[_type == "navigation"] {
  items[] {
    // Convert to lowercase
    "slug": coalesce(
      page->slug.current, 
      lower(title)
    ),
    children[] {
      "slug": page->slug.current,
    }
  }
}

You could also use string::split() and array::join() to replace spaces with hyphens:

*[_type == "navigation"] {
  items[] {
    "slug": coalesce(
      page->slug.current, 
      array::join(string::split(lower(title), " "), "-")
    ),
    children[] {
      "slug": page->slug.current,
    }
  }
}

However, GROQ has limitations for complex string manipulation:

  • No string::replace() function for character substitution (despite what you might find in searches)
  • Can't easily remove special characters, handle accents, or apply complex slugification rules
  • String functions are relatively basic compared to JavaScript

Better approach: Transform after fetching

For proper slugification (handling special characters, accents, multiple spaces, etc.), I'd recommend doing the transformation in your application code after fetching the data:

import slugify from 'slugify' // or your preferred slugify library

const data = await client.fetch(`
  *[_type == "navigation"] {
    items[] {
      "slug": coalesce(page->slug.current, title),
      children[] {
        "slug": page->slug.current,
      }
    }
  }
`)

// Transform the slugs that need it
const processed = data.map(nav => ({
  ...nav,
  items: nav.items.map(item => ({
    ...item,
    slug: item.slug?.includes?.('/') ? item.slug : slugify(item.slug, {lower: true})
  }))
}))

This gives you much more control and handles edge cases like special characters, accents, and proper URL encoding that GROQ can't easily handle on its own. The string namespace functions are great for simple transformations, but complex string manipulation is better handled in your application layer.

Show original thread
6 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?