roundly-consulting/tags-for-laravel
Add tags to any Eloquent model in Laravel. Attach, detach, and query tags through a simple, expressive API, with polymorphic relationships so posts, users, or any other model can be tagged and filtered with ease.
composer require for the package, then publish its assets: php artisan vendor:publish --tag="tags-config" and --tag="tags-migrations", followed by php artisan migrate. The migrations create the tags and taggables tables (plus a type column if you use typed tags).HasTags trait to every model that can be tagged. This gives you attachTags(), syncTags(), tags(), and the query scopes used by the popularity endpoint.config/tags.php (model bindings, tag types, slug/name handling) and at the package's route/controller registration. The GET tags/{type}/popular?on= route is the fastest way to confirm the package is wired correctly.GET /tags/post/popular?on=App\Models\Post and verify you get an ordered list of tags with counts. That single request exercises tagging, the type filter, the on model resolution, and the counting query.attachTags(['laravel'], 'topic')) so {type} in the popular route has something meaningful to filter on. Keep tag types in config rather than scattering string literals across controllers.on= for per-entity popularity: The on parameter decides which taggable model the counts are derived from. Pass the morph alias (or FQCN, depending on your config) matching the resource being viewed. This lets one tags table serve posts, videos, and products with independent popularity lists.Taggable model automatically excludes other tenants' rows. Do not hand-roll where tenant_id = ... on the tag table; the count is over the taggable side.Cache::remember() keyed by type + on + tenant, and invalidate on tag attach/detach via model events or an observer.withCount('tags') by hand — you get consistent ordering, tie-breaking, and scope handling.on. Keep both paths pointed at the same underlying query so behaviour stays identical.withTrashed() / withoutGlobalScopes() on the taggable query explicitly. Do not assume the fix makes the endpoint unusable for auditing — it just defaults to scope-respecting behaviour.on. An unrecognised or non-taggable model in on= should be treated as a 422 rather than silently returning an empty or global list. Guard it in a form request or at the route level.taggables.taggable_type/taggable_id and on tags.type. The popular query groups over the pivot table; without those indexes it degrades quickly at scale.How can I help you explore Laravel packages today?