How to slugify or transform string values inside GROQ queries?

6 replies
Last updated: Jun 23, 2021
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
i don’t believe there is a pattern for this type of replace/string modification in groq
you could however fetch the first child? to be the default slug
i regularly slugify titles for things like FAQ sections though, probably something similar you are after, but i do that in the frontend.
you could also maybe auto-generate a slug in a hidden custom field if you really don’t want to do it in the frontend and don’t want the client to see it in the editor?
Oooh ok I figured that might be the case. Good tip RE the hidden field, I think I’ll just do it client side though 🙂
yeah more control that way, hiding things never ends well.

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?