Add validation rule to ensure string starts and ends with specific character

8 replies
Last updated: Sep 14, 2022
How would I go about adding a rule in a sanity field to make sure whatever string is put into that field begins and ends with a certain character?
So for slugs, I want to make sure that when a slug is added, the user adds a beginning and trailing
/
and if they do not it will not allow them to save/publish until they add that.
AI Update

You can use Sanity's custom validation with the custom() method to enforce that a string starts and ends with a specific character. Here's how to validate that slugs begin and end with /:

defineField({
  name: 'slug',
  title: 'Slug',
  type: 'string',
  validation: (rule) =>
    rule.custom((slug) => {
      // Allow undefined/empty values if the field is optional
      if (!slug) {
        return true;
      }
      
      // Check if slug starts and ends with /
      if (!slug.startsWith('/') || !slug.endsWith('/')) {
        return 'Slug must begin and end with a forward slash (/)';
      }
      
      return true;
    }),
})

If you want to make the field required AND enforce the slash rule, you can chain the validations:

defineField({
  name: 'slug',
  title: 'Slug',
  type: 'string',
  validation: (rule) => [
    rule.required().error('Slug is required'),
    rule.custom((slug) => {
      if (!slug) return true; // Let the required() rule handle empty values
      
      if (!slug.startsWith('/') || !slug.endsWith('/')) {
        return 'Slug must begin and end with a forward slash (/)';
      }
      
      return true;
    }).error('Invalid slug format'),
  ],
})

This validation will prevent users from saving or publishing the document until they add the required slashes at both ends. The error message will appear in real-time as they type, giving them immediate feedback.

You can also use a regex pattern if you prefer:

validation: (rule) =>
  rule.custom((slug) => {
    if (!slug) return true;
    const regex = /^\/.*\/$/;
    return regex.test(slug) ? true : 'Slug must begin and end with /';
  }),

The custom validation documentation provides more details on creating validation rules tailored to your specific requirements.

Show original thread
8 replies
hello
user M
đź‘‹You can use
validations for that!
required()
will make it impossible to publish unless it is full-filled in and the
custom
validation is whatever you want the condition to be.But we have a
slug
field ready for use, you know that?
...
validation: Rule =>
    Rule.required().custom(YOUR_CUSTOM_VALIDATION)
...
You can use the method
includes()
to check for the / in the string, and setup a conditional
if else
statement
Hey
user J
! Thanks for this. I was just looking into this on the docs. I was aware of the slug field I just wanted to add a little extra customization/required-ness to it.
Thanks!
user J
I have a interesting problem.
Currently I want to use the
type: 'slug'
because of how we check for unique slug values in the options array.
However, if I have the type as slug I cannot use a custom function to check if the string starts and ends with
/

But if I change it to string, I cannot use the
.unique()
validation method and therefore I can have multiple strings be the same.
Do you know a way I can add custom validation to slugs? Im not sure why my custom validation always errors/doesn't work with slugs unless slugs are stored differently.
you can use the validation for unique in a slug and add the / in your frontend…. let me check how I once did this
Okay here we have it:
{
            title: 'Slug',
            name: 'slug',
            type: 'slug',
            inputComponent: SlugInput,
            readOnly: false,
            options: {
                basePath: basePath,
                isUnique: isUniqueAcrossAllDocuments,
                source: 'title',
                maxLength: 200, // will be ignored if slugify is set
                slugify: input => input
                    .toLowerCase()
                    .replace(/\s+/g, '-')
                    .slice(0, 200)
            }
        },
This is similar to the
.unique()
validation and should do the trick
// /lib/isUniqueAcrossAllDocuments.js
import client from 'part:@sanity/base/client'

// Note: this assumes that every document that has a slug field
// have it on the `slug` field at the root
export function isUniqueAcrossAllDocuments(slug, options) {
    const { document } = options

    const id = document._id.replace(/^drafts\./, '')
    const params = {
        draft: `drafts.${id}`,
        published: id,
        slug
    }

    const query = `!defined(*[!(_id in [$draft, $published]) && slug.current == $slug][0]._id)`

    return client.fetch(query, params)
}
A check function can be then added like the one above and set accordingly… hope that helps!
user J
thank you so much. I forgot to respond but I got it working as it is expected too.
hey
user J
I have an additional question about the unique field.
I manipulated the function in such to make the query fetch all documents (because i want to check the slug field in all types). But in my case the
slug
field is not global but within another type called
page
.
I created another type called
case
and call the unique function within this type.
when i use the query
*[slug.current == $slug || page.slug.current == $slug]
no matter what it only gives me results back from the
case
type but not from the `page`type. If i use the query within the vision tool it works as intended.
TL;DR why does the query from the unique field behave differently (scoped) then just in a regular use case? how can i prevent the query from being scoped?

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?