How to lock slug field and customize apostrophe handling in Sanity slugs?

10 replies
Last updated: Jan 20, 2022
A couple of questions as I couldn't find anything using the docs search, is it possible to lock an input? For example the slug field, once generated I don't want the editor to change it. I thought it was demoed in the last open house but I couldn't find anything. My second question along the same lines, when I have a title like
VMI's RB staying for an extra year
, the slug will be something like
vmi-s-rb-staying-for-an-extra-year
. Is there a way to tell sanity that if a word has an
'
, don't put a hyphen? So something like
vmis-rb-staying-for-an-extra-year
?
AI Update

Great questions! Let me address both of them.

Locking/Making Fields Read-Only

Yes, you can absolutely lock a field like slug once it's generated! Sanity supports the readOnly option on fields, and it can accept either a boolean or a callback function to conditionally lock fields. For your slug use case, you could do something like this:

{
  name: 'slug',
  type: 'slug',
  options: {
    source: 'title'
  },
  readOnly: ({document}) => {
    // Lock the slug once it has been generated
    return !!document?.slug?.current
  }
}

Or if you want to lock it only after the document is published:

{
  name: 'slug',
  type: 'slug',
  options: {
    source: 'title'
  },
  readOnly: ({document}) => {
    // Lock after first publish
    return document?._id && !document._id.startsWith('drafts.')
  }
}

The readOnly callback receives parameters like document, parent, value, and currentUser, giving you full control over when the field should be locked.

Custom Slug Handling for Apostrophes

For your second question about handling apostrophes in slugs (so "VMI's RB" becomes vmis-rb instead of vmi-s-rb), you can use the slugify option on your slug field. The slug type supports custom slugification logic through this option:

{
  name: 'slug',
  type: 'slug',
  options: {
    source: 'title',
    slugify: input => input
      .toLowerCase()
      .replace(/'/g, '') // Remove apostrophes first
      .replace(/\s+/g, '-') // Replace spaces with hyphens
      .replace(/[^\w\-]+/g, '') // Remove all non-word chars except hyphens
      .replace(/\-\-+/g, '-') // Replace multiple hyphens with single hyphen
      .replace(/^-+/, '') // Trim hyphens from start
      .replace(/-+$/, '') // Trim hyphens from end
  }
}

This custom function strips out apostrophes before converting spaces to hyphens, so "VMI's RB staying for an extra year" will become vmis-rb-staying-for-an-extra-year instead of vmi-s-rb-staying-for-an-extra-year.

You can customize this function further to handle any other special characters or patterns specific to your content needs!

Show original thread
10 replies
In the Slug schema type docs it looks like the readOnly can take a conditional callback (wherein my thinking would be, is it empty? If so, don't let them touch it again), and the slugify option (maybe?) takes a callback to let you override its functionality. Or is that inaccurate?
Ah ok, I would still just need to update the
replace()
as it still takes
VMI's
and makes it
vmi-s
instead of
vmis
. It just looks weird having
vmi-s-rb
🤷
.replace(/[^\w\-]+/g, '')
should handle that.
All I did was click
generate
with
{
      name: "slug",
      title: "Slug",
      description:
        "Press generate to generate the slug automatically, do not manually input slug",
      type: "slug",
      readOnly: ({ document }) => document?.slug,
      slugify: (input) =>
        input
          .toLowerCase()
          .trim()
          .replace(/\s+/g, "-")
          .replace(/[^\w\-]+/g, "")
          .replace(/\-\-+/g, "-"),
      options: {
        source: "title",
        maxLength: 96,
      },
      validation: (Rule) => Rule.required(),
    },

Also, is it possible to make it ready only once it has been published? Kind of sucks if you are doing everything in order and then bam you need to change the title šŸ˜…
Sorry James—I’m not sure what the issue might be. I just tested that exact slugify function and it game me
vmis-rb-staying-for-an-extra-year
. Maybe try a hard refresh of your browser or restarting your studio process in the terminal?
For your latest question, you could look at using a
document action to set the slug to read-only before publishing. This code might give a starting point or some inspiration, as well. Depending on how much leeway you want to give your editors, you could build in a boolean or other logic to make the slug field writable again. For example, if the title is deleted this would let you set the slug again:

readOnly: ({ document }) => document?.title && document?.slug,
Yeah I am still getting the issue šŸ˜• I restarted the studio, I even opened the studio in a browser I haven't been using to run the studio in. I have now tried in both Safari and Chrome with no luck. Not sure what I am doing wrong... Because even when deploying the studio, it still adds the
-

{
      name: "slug",
      title: "Slug",
      description:
        "Press generate to generate the slug automatically, do not manually input slug",
      type: "slug",
      readOnly: ({ document }) => document?.title && document?.slug,
      slugify: (input) =>
        input
          .toLowerCase()
          .trim()
          .replace(/\s+/g, "-")
          .replace(/[^\w\-]+/g, "")
          .replace(/\-\-+/g, "-"),
      options: {
        source: "title",
        maxLength: 96,
      },
      validation: (Rule) => Rule.required(),
    },
Yeah I am still getting the issue šŸ˜• I restarted the studio, I even opened the studio in a browser I haven't been using to run the studio in. I have now tried in both Safari and Chrome with no luck. Not sure what I am doing wrong... Because even when deploying the studio, it still adds the
-

{
      name: "slug",
      title: "Slug",
      description:
        "Press generate to generate the slug automatically, do not manually input slug",
      type: "slug",
      readOnly: ({ document }) => document?.title && document?.slug,
      slugify: (input) =>
        input
          .toLowerCase()
          .trim()
          .replace(/\s+/g, "-")
          .replace(/[^\w\-]+/g, "")
          .replace(/\-\-+/g, "-"),
      options: {
        source: "title",
        maxLength: 96,
      },
      validation: (Rule) => Rule.required(),
    },
Sorry… I didn’t pay close enough attention. The
slugify
function needs to be inside of the
options
object.
🤦 now it is working šŸ˜…

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?