bugo/moonshine-title-with-slug
Install the package with Composer and confirm the MoonShine version constraint before wiring it into a resource:
composer require dragomano/moonshine-title-with-slug
First places to look:
TitleWithSlug::make(...) signature and supported MoonShine major version.composer.json for the moonshine/moonshine constraint; MoonShine 3 field APIs differ from 2.x.resources/views if you need to customize the two-input markup.src/Fields for the field class and any exposed configuration methods.The first practical use case is a blog post or page resource where an editor types a title and expects a URL slug to be generated automatically. Replace the usual pair of Text fields with the package field:
use Dragomano\MoonshineTitleWithSlug\Fields\TitleWithSlug;
public function fields(): array
{
return [
TitleWithSlug::make('title', 'slug'),
// ...
];
}
Confirm the argument order in the README for 0.1; the key point is that the field owns both the title and slug columns.
Basic CRUD integration. Add the field to the resource’s fields() method and remove the separate title/slug inputs. Keep the underlying columns fillable on the model and present in the migration.
Validation belongs in the resource or a FormRequest. The field renders two inputs, but do not rely on field-level rules alone. Validate both columns and enforce slug uniqueness:
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => [
'required',
'string',
'max:255',
Rule::unique('posts', 'slug')->ignore($this->getItem()?->getKey()),
],
];
}
Add a database unique index on slug as the final safeguard.
Use the slug for route model binding. On the model:
public function getRouteKeyName(): string
{
return 'slug';
}
Then generate URLs with route('posts.show', $post). If editors can change slugs after publishing, store old slugs and add redirects so existing links do not break.
Keep index and detail views readable. A composite field is usually form-oriented. Hide it on index and show a normal text field instead:
TitleWithSlug::make('title', 'slug')->hideOnIndex(),
Text::make('Slug', 'slug')->hideOnForm(),
Add a server-side fallback for imports and API writes. The package’s auto-generation is primarily an admin-form convenience. For seeders, imports, or API-created records, normalize the slug in the model:
use Illuminate\Support\Str;
protected static function booted(): void
{
static::saving(function (self $post) {
if (blank($post->slug)) {
$post->slug = Str::slug($post->title);
}
});
}
Decide the overwrite policy early. For drafts, regenerating the slug as the title changes is useful. For published content, changing the title should not silently rewrite the URL. If the package always regenerates, lock the slug field after publication or enforce the rule in a model observer.
Test inside MoonShine modals and relation forms. MoonShine uses Alpine/Livewire-style interactivity. If the field works on a full-page form but not in a modal, the package’s JavaScript may not be initializing after the dynamic render.
0.1.*), and review the diff before upgrading to 0.2.Str::slug, Cyrillic, accented characters, and emoji may produce different results. Pick one source of truth or make the server-side normalizer match the frontend behavior.php artisan optimize:clear and any MoonShine asset/install command recommended by the README. Stale compiled assets are a common reason the slug script appears not to run.title and slug under the names your rules expect. If the package uses array names or nested keys, adjust the resource rules accordingly.slug column with a normal Text field for index filters and searchable columns.How can I help you explore Laravel packages today?