Slugify
Generate PocketBase slugs from one or more record fields.
The plugin combines configured input fields in order, normalizes the text to lowercase alphanumeric segments separated by hyphens, truncates the result to the configured length, and adds numeric suffixes when needed to keep slugs unique.
Installation
Build PocketBase with the plugin:
xpb build --with github.com/mjadobson/pb-plugin-slug@latest
Setup
On startup the plugin creates a shared _plugins collection if it doesn't already exist.
Create or update the slugify row in _plugins with:
plugin_name: slugify
enabled: true or false
config: JSON config
Example config value:
[
{
"collection_name": "posts",
"input_fields": ["title", "docs.name"],
"output_field": "slug",
"length": 64,
"recalculate": true
},
{
"collection_name": "pages",
"input_fields": ["title"],
"output_field": "slug",
"length": 32
}
]
Then make sure each target collection has:
- the fields listed in
input_fields
- a text or editor field matching
output_field
- a single-column
UNIQUE index on output_field (recommended with WHERE output_field != '')
The plugin runs after successful record create and update operations.
It reloads its active config whenever any row in _plugins is created, updated, or deleted.
It also reloads when collections are created, updated, or deleted so deferred configs can become active after schema changes.
It does not backfill slugs for existing records unless you set recalculate: true on a config entry.
Unique Index Requirement
output_field must have its own single-column unique index.
This plugin checks for existing slugs before saving, but concurrent writes can still race unless the database also enforces uniqueness. The unique index is what guarantees two records cannot end up with the same slug.
For SQLite-backed PocketBase collections, a good pattern is:
CREATE UNIQUE INDEX `idx_posts_slug_unique` ON `posts` (`slug`) WHERE `slug` != ''
Replace posts and slug with your collection and field names.
Plugin Config
[].collection_name
The PocketBase collection name to watch.
The fields to combine, in order, before generating the slug.
Use dotted paths such as docs.name to pull values from related collections.
Intermediate segments must be relation fields.
[].output_field
The text or editor field where the slug should be written.
[].length
The maximum slug length.
[].recalculate
Optional.
When set to true, the plugin rewrites that config entry to recalculating: true, recalculates matching records in batches of 100, and then removes recalculating when the pass finishes.
Behaviour
- Input field values are joined with spaces in config order.
- Array-like input values are flattened and joined with spaces before slugging.
- Accents are removed where possible.
- Non-alphanumeric characters become hyphens.
- Repeated separators are collapsed and trimmed.
- Slugs are lowercased and truncated to
length.
- If the slug already exists in the same collection,
-1, -2, and so on are appended while staying within length.
- If all configured inputs are blank, the output slug is cleared.
- The output field must have its own unique index so concurrent writes cannot create duplicate slugs.
- Invalid config entries are skipped individually so valid rules can continue to run.
Development
go mod tidy
GOCACHE=/tmp/go-build go test ./...
GOCACHE=/tmp/go-build go build ./...