How to enforce proper slug format validation in Sanity to prevent spaces and capitals?

7 replies
Last updated: May 5, 2020
I noticed today that
type: 'slug'
can have spaces and capitalizations in it. Someone on our team instead of clicking "Generate" on the slug typed something in and we didn't catch it before it was published which kinda messed things up. I can go back and correct it obviously but what is the best way to enforce a
type: 'slug'
to actually be a slug (at least the way I think of a slug) - do a custom validation against capital letters and spaces?
AI Update

You're absolutely right to be concerned about this! The slug field in Sanity doesn't actually enforce URL-friendly formatting by default when someone manually types into it - it only applies slugification when you click the "Generate" button. This is a common gotcha.

The best approach is to add custom validation to your slug field. Here's how you can enforce proper slug formatting:

{
  name: 'slug',
  type: 'slug',
  options: {
    source: 'title',
    maxLength: 96,
  },
  validation: Rule => Rule.required().custom((slug) => {
    if (!slug || !slug.current) {
      return 'Slug is required'
    }
    
    const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
    
    if (!slugRegex.test(slug.current)) {
      return 'Slug must be lowercase, contain only letters, numbers, and hyphens (no spaces or special characters)'
    }
    
    return true
  })
}

This regex pattern ensures the slug:

  • Contains only lowercase letters and numbers
  • Uses hyphens (not spaces) to separate words
  • Doesn't start or end with a hyphen
  • Has no special characters or uppercase letters

Additional options to consider:

  1. Make it read-only to force using the Generate button:
{
  name: 'slug',
  type: 'slug',
  options: {
    source: 'title',
  },
  readOnly: true, // Users can only use Generate button
}
  1. Custom slugify function to handle edge cases when the Generate button IS used:
{
  name: 'slug',
  type: 'slug',
  options: {
    source: 'title',
    slugify: input => input
      .toLowerCase()
      .replace(/\s+/g, '-')
      .replace(/[^\w-]+/g, '')
      .slice(0, 96)
  }
}

The validation approach is usually best because it catches the problem before publishing while still allowing manual editing when needed (like fixing typos). The readOnly approach is more restrictive but prevents the issue entirely if you never need manual slug editing.

Remember that the slug value is stored in slug.current, which is why the validation checks slug.current rather than just slug. You can also combine validation with a custom slugify function for comprehensive protection - the slugify handles the Generate button behavior, while validation catches any manual entries.

Show original thread
7 replies
Or just use my own
slugify
function
Hi User, I think it’s surprising this only seems to have come up now because, indeed, it does allow spaces and caps by default. I’m not sure if it should, so thanks for reporting.
Unless I’m overlooking something, I think using a custom
slugify
function would just handle string normalisation via the Generate button but not necessarily resolve the validation (or lack thereof) issue.
If you want to go down the RegEx route, you could try using a regular expression like this:

/^
  [a-z0-9]+   # One or more repetition of given characters
  (?:         # A non-capture group.
    -           # A hyphen
    [a-z0-9]+   # One or more repetition of given characters
  )*          # Zero or more repetition of previous group
 $/
Haven’t tested yet, but implemented that would look something like this:

...
validation: Rule =>
      Rule.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
...
Hi User, I think it’s surprising this only seems to have come up now because, indeed, it does allow spaces and caps by default. I’m not sure if it should, so thanks for reporting.
Unless I’m overlooking something, I think using a custom
slugify
function would just handle string normalisation via the Generate button but not necessarily resolve the validation (or lack thereof) issue.
If you want to go down the RegEx route, you could try using a regular expression like this:

/^
  [a-z0-9]+   # One or more repetition of given characters
  (?:         # A non-capture group.
    -           # A hyphen
    [a-z0-9]+   # One or more repetition of given characters
  )*          # Zero or more repetition of previous group
 $/
Haven’t tested yet, but implemented that would look something like this:

...
validation: Rule =>
      Rule.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
...
Yeah it looks like the
slugify
option is only triggered when the user clicks "Generate" - custom validation must be the way to go
validator for flag "regex" not found
My studio is up to date, am I missing something?
Hi again User, sorry about that. I’m not sure what causes that notation to work in some types and not in others - perhaps it’s the object causing difficulties. Anyway, just tested and this approach seems to work🤞
...    
validation: Rule => Rule.custom(slug => {
      const regex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
      return regex.test(slug.current)
    }),
...
Thanks!

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?