nextjs-cms

slugField

A URL-friendly identifier generated from another text field as the editor types.

import { slugField } from 'nextjs-cms/core/fields'

Basic usage

A slug always follows a source textField, passed as for:

const title = textField({ name: 'title', required: true })
const slug = slugField({ name: 'slug', for: title })

Typing "Hello, World! 2026" in the title fills the slug with hello-world-2026. Editors can still edit the slug by hand.

With every option

const title = textField({ name: 'title', required: true, localized: true })

const slug = slugField({
    name: 'slug',
    label: 'URL slug',
    for: title,
    required: true,
    localized: true,
    order: 2,

    placeholder: 'generated-from-the-title',
    minLength: 3,
    maxLength: 80,
    defaultValue: undefined,
    editProtection: {
        enabled: true,
        alert: {
            title: { en: 'Changing the URL', ar: 'تغيير الرابط' },
            description: {
                en: 'Existing links to this page will break. Add a redirect on the website first.',
                ar: 'ستتعطل الروابط الحالية لهذه الصفحة.',
            },
        },
    },
})

// Make it unique (recommended)
db: { table: 'posts', unique: [{ columns: [slug] }] }

Options

OptionTypeDefaultDescription
fortextField configrequiredThe source field the slug is generated from.
editProtectionboolean | { enabled?, alert? }falseOn edit forms, stop the slug from following the source field automatically.
placeholderstringnoneHint in the empty input.
minLengthnumbernoneMinimum length, checked after normalization.
maxLengthnumbernoneMaximum length, checked after normalization.
defaultValuestringnoneInitial value.

Plus the common options.

How slugs are normalized

The value is normalized on the server on every save, whatever the editor typed:

  1. Lowercased and trimmed.
  2. Whitespace runs become -.
  3. Everything except letters, numbers and - is removed. Letters from any script are kept, so Arabic, Cyrillic or accented slugs work (مرحبا-بالعالم, café-crème).
  4. Repeated hyphens are collapsed, and leading/trailing hyphens removed.
InputStored slug
Hello, World!hello-world
Next.js 16 Release nextjs-16-release
Crème brûléecrème-brûlée
مرحبا بالعالممرحبا-بالعالم
Good to know:

Slugs keep non-ASCII letters. If your website needs pure ASCII URLs, transliterate in a beforeCreate/beforeUpdate hook.

Options in detail

Uniqueness

slugField doesn't enforce uniqueness by itself. Add it to db.unique:

db: { table: 'posts', unique: [{ columns: [slug] }] }

The CMS checks it before saving and shows a readable error naming the field, instead of a raw MySQL duplicate-key error. For a localized slug, uniqueness is enforced per locale.

Unique per category (two categories can each have an "intro" post):

db: { table: 'posts', unique: [{ columns: [categoryId, slug] }] }

editProtection

Once a page is published, its URL is shared, bookmarked and indexed. Auto-updating the slug when someone fixes a typo in the title silently breaks those links. editProtection keeps the slug frozen on edit forms (new items still auto-generate):

// Simple on/off
slugField({ name: 'slug', for: title, editProtection: true })

With alert, editors who try to change a protected slug see a warning first. Both texts can be translated:

slugField({
    name: 'slug',
    for: title,
    editProtection: {
        enabled: true,
        alert: {
            title: 'This changes the public URL',
            description: 'Links shared before this change will stop working.',
        },
    },
})

Localized slugs

Make both the source and the slug localized to get a slug per language:

const title = textField({ name: 'title', localized: true, required: true })
const slug = slugField({ name: 'slug', for: title, localized: true, required: true })

Your website then resolves /en/hello-world and /ar/مرحبا-بالعالم to the same item. See Localization.

Storage

ColumnVARCHAR(255) CHARACTER SET utf8mb4
ValueThe normalized slug