Many sites show their subpages under readable URL addresses instead of selecting records by their id.

Generating slugs

Set $sluggable to the field the slug is generated from. admin:migrate adds an indexed slug column with the length of that field (nullable when the field is not required), and the slug is regenerated on every save.
app/Models/Article.php
The value is transliterated, lowercased, and non alphanumeric characters are replaced by dashes: Žltý kôň & Spol. 2026! becomes zlty-kon-spol-2026.
Generated slug column in the database table
When the slug already exists, an incremental suffix is appended. Three records named My article get the slugs my-article, my-article-2 and my-article-3. Saving a record whose name produces the same base keeps its current suffix.
When you add $sluggable to a model whose table already contains rows, admin:migrate generates slugs for all existing rows. If the field does not exist, the migration prints Unknown slug column and no column is created.

Multilanguage slugs

If the slug source field has the locale parameter, the slug column is stored as JSON holding a slug for every language. Duplicate slugs get an increment only in the language where they collide, and the column has no database index.
app/Models/Article.php
Slug column stored as JSON with a value per language
getSlug() returns the slug of the current language, then the slug of the default language, then any available one.

Selecting a record by its slug

findBySlug() returns the record or false, findBySlugOrFail() aborts with 404. Both work as query scopes, so previous constraints apply.
app/Http/Controllers/ArticleController.php
Localized slugs are searched in the current language. Records without a slug in the current language are found by the slug of the default language. An array as the second argument selects columns: Article::findBySlug($slug, ['id', 'slug', 'name']).

By slug and id

Pass the record id as the second argument. When the slug in the URL is outdated, the visitor is redirected with 301 to the same route with the current slug, and the query string is kept.
app/Http/Controllers/ArticleController.php

Slug history

With history.sluggable enabled, every changed slug is stored in the sluggable_histories table, and findBySlug() / findBySlugOrFail() redirect old URLs with 301 to the current slug.
config/admin.php
Run php artisan admin:migrate after enabling it. Marking routes with seo() and rendering the meta tags is described in SEO. A single model can opt out:

SEO meta tags

$seo, editing slugs in the administration and the SEO of routes: available with the crudadmin/website package (composer require crudadmin/website).
$seo adds meta tag fields into a separate form tab and the database: meta_title, meta_keywords, meta_description, meta_canonical_url and meta_image (multiple images).
Use a group id instead of true to place the fields into an existing tab of the form:
With localization enabled, title, keywords and description are translatable. Keep them as plain columns with:
Meta attributes are included in toArray() and JSON responses by default. Use $hidden or makeHidden() to exclude them. The SEO helper reads them directly from a model:

Editing slugs in the administration

Models with $seo show the slug in the SEO tab together with the Automatic url checkbox (slug_dynamic). When it is unchecked, the manually entered slug is kept even when the name changes. The slug field is validated as unique across records which are not deleted (unique_json for localized slugs). Disable the rule when slugs may repeat:

SEO of routes

The seo configuration enables the SEO routes module, where administrators set meta tags of any URL of the website.
config/admin.php
Run php artisan admin:migrate after enabling it.