cortex/tags
Cortex Tags provides a Laravel tagging system for associating reusable tags with your models. It includes helpers for managing tags and querying tagged records, making it easy to organize and filter content.
cortex/tags is not a tagging engine — it's the HTTP/admin layer that sits on top of Cortex's polymorphic taggable backend. Before you write a line of code, confirm you actually need this package: if you want $post->attachTag('laravel') in your app, you want the backend package (cortex/taggable or its Rinvex equivalent), not this one. Reach for cortex/tags only when you want ready-made controllers/routes/requests for managing tags over HTTP.
Minimal steps:
composer.json before installing and note the laravel/framework, php, and Cortex-sibling version constraints. On anything newer than Laravel 6/7 you will likely need --ignore-platform-reqs or a fork. This is the single biggest decision point.composer require cortex/tags will pull the taggable layer as a dependency; verify the resolved versions in composer.lock rather than trusting the README.config/app.php and any cortex/fort bootstrap config.php artisan vendor:publish --tag=cortex-tags-migrations followed by php artisan migrate. Confirm you now have a tags table and a taggables morph pivot.Post (or whatever you're tagging) and verify $post->tags returns a collection.GET the tag index. If it renders or returns JSON, the frontend layer is wired correctly — that's your smoke test before touching config.First real use case: an admin screen that lists, creates, edits, and deletes tags, with a slug-based public URL (/tags/laravel) for browsing tagged content.
Treat the HTTP layer as optional scaffolding. The most common day-to-day pattern is to keep the backend trait and replace the package controllers with your own thin resource controller. You get the polymorphic relations and slugging for free, and full control over authorization, pagination, and response shape.
Attach on write, read via relation. In a form request or action class, sync tags on save:
$post->syncTags($validated['tags']); // IDs or names, depending on backend API
Keep attach/detach inside a transaction with the parent save so a failed tag write doesn't leave a half-saved post.
Use slugs as the public contract. The Sluggable integration means tag URLs should be /tags/{tag:slug}. Enable route model binding on the slug column and never expose raw IDs in links — renaming a tag then doesn't break inbound links if you keep the old slug as a redirect.
Lean on Translatable for display, not identity. Store tag identity as the slug (locale-independent) and translate the name. Render with $tag->getTranslation('name', app()->getLocale()) and always provide a fallback locale.
Compose with an admin panel carefully. If you're already on cortex/fort, let it own routing/auth. If you're not, don't half-adopt it — mount the package routes behind your own auth/can:manage-tags middleware group in a service provider.
Cache the read path. $post->tags and the tag index are read far more than written. Cache per-post tag lists and the full tag cloud, and bust them in the attach/detach path (model events on the pivot are the reliable hook).
Extend by substitution, not modification. Bind your own Tag model and controller in the container/config rather than editing vendor files — the package will never receive upstream fixes, so your overrides are the real codebase.
spatie/laravel-tags / rinvex/laravel-tags if this is a long-lived project. Do the migration cost estimate before the prototype.taggables.taggable_type stores fully-qualified class names, renaming or moving a model silently orphans every tag relationship. Register a Relation::morphMap() early, before production data exists.onDelete('cascade') on the morph columns. Classic Cortex migrations often didn't; add it in your own migration if missing, otherwise deleting a post leaves dangling taggables rows that break counts.laravel. If you need per-tenant or per-type tag namespaces, override the Sluggable behavior with a scoped uniqueness rule rather than relying on the default.config/cortex-tags.php and sometimes from a settings repository. When a route prefix or middleware change "doesn't take", check both and clear config cache (php artisan config:clear).DB::listen() or ->toSql() on the morphToMany — nine times out of ten it's a mismatched morph type or a missing taggable_id index, not a package bug.How can I help you explore Laravel packages today?