@kilivi/payloadcms-localized-slugs
v1.1.6
Published
Localized slugs plugin for Payload CMS v3 with multi-locale support
Maintainers
Readme
@kilivi/payloadcms-localized-slugs
Localized slugs plugin for Payload CMS v3 with multi-locale support. Automatically copies and manages URL-friendly slugs for collections with internationalization (i18n) capabilities.
Features
- 🌐 Multi-locale slug support (Czech, English, and extensible)
- � Copy slug/fullPath values from existing localized fields to
localizedSlugs - 🎯 Automatic localization detection (handles both localized and non-localized fields)
- 🔗 Full path support for hierarchical content
- ⚙️ Configurable per-collection settings
- 📦 Zero dependencies (Payload only)
- ✅ TypeScript support with full type safety
- 🎨 Client-side components for frontend integration
- ✅ Fully compatible with multitenant plugins - no infinite loops!
Documentation
- 📖 Integration Guide - Start here! Complete setup and troubleshooting
- 📖 Client Components Usage Guide - How to use client components in your Next.js app
- 🚀 Quick Start Guide
- 🏢 Multi-Tenant Guide
- 📦 NPM Deployment Guide
Installation
npm install @kilivi/payloadcms-localized-slugs
# or
pnpm add @kilivi/payloadcms-localized-slugs
# or
yarn add @kilivi/payloadcms-localized-slugsBasic Usage
import { localizedSlugsPlugin } from '@kilivi/payloadcms-localized-slugs'
export default buildConfig({
plugins: [
localizedSlugsPlugin({
enabled: true,
locales: ['cs', 'en'],
collections: [
{
collection: 'pages',
slugField: 'slug',
fullPathField: 'fullPath',
},
],
enableLogging: true, // Show debug logs
}),
],
})How It Works
The plugin automatically:
- Detects localized fields - Works with both
localized: trueand regular fields - Copies values - Takes
slugandfullPathvalues and copies them tolocalizedSlugs - Creates a computed field - Stores the localized slugs for easy access
- Prevents infinite loops - Uses internal flags to prevent multitenant recursion
Example Output
Input Collection:
{
name: 'slug',
type: 'text',
localized: true, // Must be localized!
}
{
name: 'fullPath',
type: 'text',
localized: true, // Must be localized!
}Output Document:
{
"slug": {
"en": "contact-us",
"cs": "kontaktujte-nas"
},
"fullPath": {
"en": "/contact-us",
"cs": "/kontaktujte-nas"
},
"localizedSlugs": {
"en": {
"slug": "contact-us",
"fullPath": "/contact-us"
},
"cs": {
"slug": "kontaktujte-nas",
"fullPath": "/kontaktujte-nas"
}
}
} {
collection: 'pages',
slugField: 'urlSlug',
generateFromTitle: true,
titleField: 'pageTitle',
},
],
enableLogging: true,
}),], })
## Configuration
### Plugin Options
```typescript
interface LocalizedSlugsPluginOptions {
/** Whether the plugin is enabled (default: true) */
enabled?: boolean
/** Array of supported locales (default: ['cs', 'en']) */
locales?: ('cs' | 'en')[]
/** Default locale (default: 'cs') */
defaultLocale?: 'cs' | 'en'
/** Collections configuration */
collections?: CollectionConfig[]
/** Enable console logging (default: false) */
enableLogging?: boolean
/** Custom diacritics mapping for slug generation */
customDiacriticsMap?: Record<string, string>
}Collection Configuration
interface CollectionConfig {
/** Collection slug */
collection: string
/** Field name for the slug (default: 'slug') */
slugField?: string
/** Field name for the full path (default: 'fullPath') */
fullPathField?: string
/** Auto-generate slugs from title field (default: false) */
generateFromTitle?: boolean
/** Title field name for auto-generation (default: 'title') */
titleField?: string
}How It Works
Slug Generation
The plugin automatically generates URL-friendly slugs from titles:
- Converts text to lowercase
- Removes diacritics (ž → z, č → c, etc.)
- Replaces spaces and special characters with hyphens
- Removes duplicate hyphens
- Trims leading/trailing hyphens
Example:
"Čeština" → "cestina"
"Hello World" → "hello-world"
"C++ Programming" → "c-programming"Localized Slugs
Each locale gets its own slug based on the localized title:
// In your collection:
{
title: {
cs: "Přispívání",
en: "Contributing"
},
slug: {
cs: "prispivani", // Auto-generated
en: "contributing" // Auto-generated
}
}Full Path Generation
For hierarchical collections (e.g., pages with parents), the plugin generates full paths:
// Parent: /docs
// Child with slug "guides" → /docs/guides
{
slug: "guides",
fullPath: "/docs/guides" // Auto-generated
}Extending Locales
To add custom locales beyond Czech and English:
import { localizedSlugsPlugin } from '@kilivi/payloadcms-localized-slugs'
localizedSlugsPlugin({
locales: ['cs', 'en', 'sk'], // Add Slovak
defaultLocale: 'cs',
customDiacriticsMap: {
š: 's',
ž: 'z',
ť: 't',
ľ: 'l',
},
// ... rest of config
})Custom Diacritics Mapping
Override the default diacritics mapping:
localizedSlugsPlugin({
customDiacriticsMap: {
á: 'a',
é: 'e',
í: 'i',
ó: 'o',
ú: 'u',
ü: 'u',
},
})Integration with Collections
Basic Collection Setup
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
localized: true,
},
{
name: 'slug',
type: 'text',
localized: true,
admin: {
description: 'Auto-generated from title. Edit to customize.',
},
},
{
name: 'content',
type: 'richText',
localized: true,
},
],
}Hierarchical Collection (with Parent)
export const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'parent',
type: 'relationship',
relationTo: 'pages',
required: false,
},
{
name: 'title',
type: 'text',
localized: true,
required: true,
},
{
name: 'slug',
type: 'text',
localized: true,
},
{
name: 'fullPath',
type: 'text',
localized: true,
admin: {
readOnly: true,
description: 'Full URL path (auto-generated)',
},
},
],
}API Reference
createLocalizedSlugField
Creates a localized slug field configuration:
import { createLocalizedSlugField } from '@kilivi/payloadcms-localized-slugs/fields'
const slugField = createLocalizedSlugField({
locales: ['cs', 'en'],
defaultLocale: 'cs',
})createPopulateLocalizedSlugsHook
Creates a hook for automatic slug generation:
import { createPopulateLocalizedSlugsHook } from '@kilivi/payloadcms-localized-slugs/hooks'
const beforeChangeHook = createPopulateLocalizedSlugsHook({
slugField: 'slug',
titleField: 'title',
locales: ['cs', 'en'],
})Examples
Blog with Localized Posts
import { localizedSlugsPlugin } from '@kilivi/payloadcms-localized-slugs'
export default buildConfig({
collections: [
{
slug: 'posts',
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'slug', type: 'text', localized: true },
{ name: 'content', type: 'richText', localized: true },
],
},
],
plugins: [
localizedSlugsPlugin({
locales: ['cs', 'en'],
collections: [
{
collection: 'posts',
generateFromTitle: true,
},
],
}),
],
})Documentation Site with Hierarchy
localizedSlugsPlugin({
locales: ['cs', 'en'],
collections: [
{
collection: 'docs',
slugField: 'slug',
fullPathField: 'urlPath',
generateFromTitle: true,
titleField: 'title',
},
],
})Troubleshooting
Slugs Not Auto-Generating
- Ensure
generateFromTitle: truein collection config - Check that the
titleFieldmatches your field name - Verify the title field is
localized: true
Diacritics Not Converting Correctly
- Provide a
customDiacriticsMapfor unsupported characters - Check the locale matches your content
Full Paths Not Generating
- Ensure the collection has a parent relationship field
- Verify
fullPathFieldmatches your field name - Full paths only generate for hierarchical collections
Client-Side Usage
The plugin provides client components for use in your Next.js frontend application.
Installation in Next.js App
// app/layout.tsx
'use client'
import { SlugProvider } from '@kilivi/payloadcms-localized-slugs/client'
export default function RootLayout({ children }) {
return (
<html>
<body>
<SlugProvider>
{children}
</SlugProvider>
</body>
</html>
)
}Using Client Components
// app/[locale]/posts/[slug]/page.tsx
import { ClientSlugHandler } from '@kilivi/payloadcms-localized-slugs/client'
export default async function PostPage({ params }) {
const post = await getPayloadData('posts', params.slug)
return (
<div>
<ClientSlugHandler localizedSlugs={post.localizedSlugs} />
<h1>{post.title}</h1>
</div>
)
}Language Switcher Example
'use client'
import { useSlugContext } from '@kilivi/payloadcms-localized-slugs/client'
import Link from 'next/link'
export function LanguageSwitcher() {
const { state } = useSlugContext()
const { localizedSlugs } = state
return (
<nav>
{Object.entries(localizedSlugs).map(([locale, slug]) => (
<Link key={locale} href={`/${locale}/${slug}`}>
{locale.toUpperCase()}
</Link>
))}
</nav>
)
}📖 For detailed client component usage, see the Client Components Usage Guide
Performance
- Slug generation happens on document creation/update via hooks
- No runtime overhead for reads
- Scales well with collection size
- Supports batch operations
License
MIT
Author
Kilián Vít
Contributing
Contributions welcome! Please open an issue or submit a PR.
Changelog
See CHANGELOG.md for version history.
