Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Taxonomy Laravel Package

aliziodev/laravel-taxonomy

Manage categories, tags, and any hierarchical structure in Laravel. Terms live in one table, attach to any model via a polymorphic pivot, and nested-set hierarchies keep ancestor and descendant lookups to a single query.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

Build vs. buy for the taxonomy layer. Categorization looks trivial in a spec and isn't: you need one term store shared by many models, slugs unique per type, a parent/child tree, ancestor and descendant lookups that stay fast as the tree deepens, cache invalidation on every write, and repair tooling for when someone writes to the table directly. That is weeks of engineering plus the debugging that follows. This package is a single taxonomies table, one polymorphic pivot, a HasTaxonomy trait, and a nested set maintained in model hooks — adoptable in days. The buy decision is clear; the real work is the integration surface below.

Roadmap unlock — browse and filter surfaces. The scopes are the feature backlog: withTaxonomySlug and filterByTaxonomies (keyed type => slug, perfect for request-driven filters) give you faceted catalog search; withTaxonomyHierarchy gives "show this category and everything under it" in one scope; orderByTaxonomyType gives merchandised ordering; path and full_slug give you SEO-clean URLs like electronics/smartphones without a slug-tree table. If faceted browse, tag pages, or related-content are on the roadmap, this collapses several epics into wiring.

A one-way door the PM should own early: morph_type. The pivot stores taxonomable_id as numeric, UUID, or ULID, and it can't be changed after the first migration without rewriting the table. This is a cross-team architecture decision (ID strategy, service boundaries), not a sprint-level detail. Lock it in the design review before anyone runs migrate, not after.

Import and backfill feasibility — a real budget line. create() costs 4–7 queries per row; bulkCreate() measured 10,000 flat rows at 0.95s / 32 queries against 14.8s / 40,000 queries for a loop, and 1.0s / 37 queries nested against 22.0s / 59,980. That converts "we need a maintenance window and a feature flag for the catalog import" into "a seeder runs in a second." It accepts generators, so memory stays flat on large CSVs. The trade is explicit: model events don't fire, so anything on observers must stay on create() — a scope decision worth writing down, not discovering.

A multi-tenancy decision this package forces, not makes for you. No tenant_id column ships, cache keys are global unless you register resolveCacheScopeUsing(), and the unique(['slug','type','deleted_at']) index blocks two tenants using the same slug in the same type. If we're multi-tenant, "tenant-scoped taxonomy cache + index rewrite" is committed scope in the same epic. If we're not, say so out loud and revisit at the next tenancy conversation — a cross-tenant cache leak is a security incident, not a bug.

Scope discipline: types, not parallel relations. Custom type strings need no registration, and slugs are unique within a type, so featured can be both a category and a tag. The $name relationship parameter is deprecated for removal in 3.0 and selects pivot columns the migration doesn't create. Decision: every taxonomy concept is a type, never a second relationship. This is cheap now and an upgrade tax later.

What we own, and should staff for. The admin UI (the tree editor, drag-and-drop, bulk edit), any term-name localization (the meta JSON column is the hook — decide the schema before content teams fill it), per-term usage counts (there's no models relation; count from the pivot side and treat it as a reporting job), and any approval workflow or per-term permissions. None of that is in the box.

When to Consider This Package

Adopt when:

  • Laravel 11–13 / PHP 8.2+, single database, and at least two models need the same categorization concept — the polymorphic pivot is worth paying for only when it's shared.
  • Hierarchies are genuinely deeper than one level and read far more than written (catalog, CMS, docs, marketplace listings) — that's where the nested set earns its keep.
  • Filtering is a product requirement, not an afterthought: multi-term AND/OR, "has none of", type-scoped filters, depth filters.
  • The team accepts two invariants: attach methods take IDs or models (a slug string silently attaches nothing), and any raw insert or query-level delete corrupts the nested set.

Look elsewhere when:

  • The need is flat tags on one model. Native belongsToMany, a tags table, and an afternoon beats a dependency.
  • Multi-tenant SaaS, and nobody will fund the cache-scope resolver and index rewrite. The default is unsafe there.
  • You need a graph (multi-parent) rather than a strict tree. A nested set cannot represent a DAG.
  • You need per-term permissions, approval workflow, versioning, or audit history — out of scope, and bolting them on fights the design.
  • Procurement needs an SLA, indemnity, or a support contract. 245 stars and zero dependents describes a well-run solo project, not a vendor. Mitigate with a pinned version and a thin internal wrapper so a swap stays a sprint, not a quarter.
  • Category editing is a high-concurrency write path. Nested-set range updates contend; read-heavy is the sweet spot.

Adoption gates before committing: pin the version explicitly; read the 3.0 deprecation and put the upgrade in the roadmap; run a two-week spike on one secondary surface (a help-center or internal catalog, not the primary storefront); and contract-test the documented examples yourself — the package pins its README examples in its test suite, which tells you the docs are trustworthy, but your integration is still yours to prove.

How to Pitch It (Stakeholders)

For executives: This is a buy, not a build. MIT-licensed, no per-seat or per-call cost, and it replaces what is realistically three to five weeks of engineering plus the tree-corruption bugs that always follow. It directly enables features already on the roadmap — faceted browse, category landing pages with clean URLs, tag-based merchandising — at a fraction of the cost of building the hierarchy layer ourselves. Two risks, both containable and both known: the project is a single well-run maintainer with modest adoption, which we mitigate by pinning the version and keeping our integration behind a thin wrapper so we can swap; and any multi-tenant deployment needs explicit cache scoping, which we would scope and fund as part of the same epic rather than discover in production. Recommendation: approve a two-week spike on a secondary surface with a go/no-go at the end.

For engineering: The maintenance hygiene is the strongest signal here — 100% coverage enforced, PHPStan and Pint gating CI, semantic-release on conventional commits, a stated backward-compatibility policy, and documentation examples pinned by tests so the docs can't silently rot. Surface area is small: one table, one pivot, one trait, one manager. The traps are documented and specific, and should go in our onboarding notes on day one: the facade proxies a manager and is not an Eloquent builder (where() lives on the model); attach/sync/detach take IDs, never slugs, and fail silently otherwise; getDescendants()/getAncestors() read lft/rgt off the instance, so call refresh() first or use the id-keyed descendants(); never wrap tree()/flatTree()/getNestedTree() in a second Cache::remember(); never write to taxonomies with DB::table() or delete via a query, or the nested set breaks; and in multi-tenant installs, register the cache scope resolver or one tenant gets another's tree. bulkCreate() for all seeders and imports — 10k rows in ~1s — but not where model events matter. Everything else the package does in its hooks, it does for us.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style