PHPackages                             aliziodev/laravel-taxonomy - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. aliziodev/laravel-taxonomy

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

aliziodev/laravel-taxonomy
==========================

Laravel Taxonomy is a flexible and powerful package for managing taxonomies, categories, tags, and hierarchical structures in Laravel applications. Features nested-set support for optimal query performance on hierarchical data structures.

v2.12.0(1mo ago)24428.7k↓32.6%11[1 issues](https://github.com/aliziodev/laravel-taxonomy/issues)MITPHPPHP ^8.2|^8.3CI passing

Since May 28Pushed 4mo ago5 watchersCompare

[ Source](https://github.com/aliziodev/laravel-taxonomy)[ Packagist](https://packagist.org/packages/aliziodev/laravel-taxonomy)[ GitHub Sponsors](https://github.com/aliziodev)[ RSS](/packages/aliziodev-laravel-taxonomy/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (10)Dependencies (30)Versions (29)Used By (0)

[![Laravel Taxonomy](https://raw.githubusercontent.com/aliziodev/laravel-taxonomy/refs/heads/master/art/new-header.svg)](https://raw.githubusercontent.com/aliziodev/laravel-taxonomy/refs/heads/master/art/new-header.svg)

 [![codecov](https://camo.githubusercontent.com/40d195c9349cb588e0318c9146c08289ecd1f3af9a7ae45f8f13f1a291bb0288/68747470733a2f2f636f6465636f762e696f2f67682f616c697a696f6465762f6c61726176656c2d7461786f6e6f6d792f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/aliziodev/laravel-taxonomy) [![Tests](https://github.com/aliziodev/laravel-taxonomy/workflows/Tests/badge.svg)](https://github.com/aliziodev/laravel-taxonomy/actions) [![Code Quality](https://github.com/aliziodev/laravel-taxonomy/workflows/Code%20Quality/badge.svg)](https://github.com/aliziodev/laravel-taxonomy/actions)
 [![Latest Version on Packagist](https://camo.githubusercontent.com/635209a6b668f136f50a1a4df49d732d992bcd67ff71b939c78647648d81da6c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f616c697a696f6465762f6c61726176656c2d7461786f6e6f6d792e737667)](https://packagist.org/packages/aliziodev/laravel-taxonomy) [![Total Downloads](https://camo.githubusercontent.com/bf32f558126680576395df6dce358d8415f32a21cf0ee1b6b0e892703a73d409/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f616c697a696f6465762f6c61726176656c2d7461786f6e6f6d792e737667)](https://packagist.org/packages/aliziodev/laravel-taxonomy) [![PHP Version](https://camo.githubusercontent.com/28640b3526d414726f4aec34b070535eee1e6ae37ed5e8c575710d48249b552e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f616c697a696f6465762f6c61726176656c2d7461786f6e6f6d792e737667)](https://packagist.org/packages/aliziodev/laravel-taxonomy) [![Laravel Version](https://camo.githubusercontent.com/108a08bbd3a964fca4dbdee91ef3833e07cce499cc0ab6a7ea3b867d866a5409/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d3131253230253743253230313225323025374325323031332d6f72616e67652e737667)](https://laravel.com/) [![Ask DeepWiki](https://camo.githubusercontent.com/0f5ae213ac378635adeb5d7f13cef055ad2f7d9a47b36de7b1c67dbe09f609ca/68747470733a2f2f6465657077696b692e636f6d2f62616467652e737667)](https://deepwiki.com/aliziodev/laravel-taxonomy)

Manage categories, tags and any hierarchical structure in Laravel. Terms live in one table, attach to any model through a polymorphic pivot, and hierarchies are maintained as a nested set so ancestor and descendant lookups stay a single query.

[🇮🇩 Dokumentasi Bahasa Indonesia](README.id.md)

Contents
--------

[](#contents)

- [Requirements](#requirements) · [Installation](#installation) · [Configuration](#configuration)
- [Quick start](#quick-start) · [Working with taxonomies](#working-with-taxonomies) · [Bulk imports](#bulk-imports) · [Attaching to models](#attaching-to-models)
- [Query scopes](#query-scopes) · [Hierarchies](#hierarchies) · [Types](#types) · [Metadata](#metadata)
- [Caching](#caching) · [Multi-tenancy](#multi-tenancy) · [Slugs and exceptions](#slugs-and-exceptions)
- [Console commands](#console-commands) · [Examples](#examples) · [Troubleshooting](#troubleshooting)

Requirements
------------

[](#requirements)

RequirementVersionPHP8.2 or newerLaravel11, 12 or 13Installation
------------

[](#installation)

```
composer require aliziodev/laravel-taxonomy
php artisan taxonomy:install
php artisan migrate
```

`taxonomy:install` publishes the config and the migration. Pass `--force` to overwrite files that already exist — without it, existing files are left untouched and the command tells you so.

To publish individually:

```
php artisan vendor:publish --tag=taxonomy-config
php artisan vendor:publish --tag=taxonomy-migrations
```

Configuration
-------------

[](#configuration)

`config/taxonomy.php`, with the shipped defaults:

```
return [
    'table_names' => [
        'taxonomies'   => 'taxonomies',
        'taxonomables' => 'taxonomables',
    ],

    // Morph column type on the pivot: 'numeric', 'uuid' or 'ulid'.
    // Must match how YOUR models are keyed. Set before the first migrate.
    'morph_type' => 'uuid',

    'types' => collect(TaxonomyType::cases())->pluck('value')->toArray(),

    // Swap in your own model; it must extend the package's Taxonomy.
    'model' => Taxonomy::class,

    'slugs' => [
        'generate'               => true,  // auto-generate from name when omitted
        'regenerate_on_update'   => true,  // rewrite the slug when the name changes
        'consider_trashed'       => false, // count soft-deleted rows when checking uniqueness
        'regenerate_on_restore'  => true,  // resolve a conflict on restore instead of throwing
    ],

    'cache' => [
        'ttl'   => 86400, // seconds
        'scope' => null,  // see Multi-tenancy
    ],

    'migrations' => [
        'autoload' => env('TAXONOMY_AUTOLOAD_MIGRATIONS', true),
        'paths'    => [],
    ],
];
```

**`morph_type` is the one setting to get right up front.** It decides whether the pivot stores `taxonomable_id` as an integer, UUID or ULID, and it cannot be changed after you migrate without rewriting the table. Use `numeric` for the usual auto-incrementing keys.

**`migrations.autoload`** controls whether the package registers its migration path with `php artisan migrate`. Disable it when you run migrations per tenant connection:

```
'migrations' => ['autoload' => false],
```

```
php artisan migrate --path=database/migrations/tenants --database=tenant
```

Quick start
-----------

[](#quick-start)

```
use Aliziodev\LaravelTaxonomy\Facades\Taxonomy;
use Aliziodev\LaravelTaxonomy\Enums\TaxonomyType;

$electronics = Taxonomy::create([
    'name' => 'Electronics',
    'type' => TaxonomyType::Category,
    'meta' => ['icon' => 'devices'],
]);

$phones = Taxonomy::create([
    'name'      => 'Smartphones',
    'type'      => TaxonomyType::Category,
    'parent_id' => $electronics->id,
]);
```

Add the trait to any model that should carry taxonomies:

```
use Aliziodev\LaravelTaxonomy\Traits\HasTaxonomy;

class Product extends Model
{
    use HasTaxonomy;
}
```

```
$product->attachTaxonomies([$phones->id]);
$product->taxonomies;                                  // all attached terms
Product::withTaxonomySlug('smartphones')->get();       // filter by slug
```

Working with taxonomies
-----------------------

[](#working-with-taxonomies)

The `Taxonomy` facade proxies `TaxonomyManager` and exposes exactly these methods:

```
Taxonomy::create($attributes);                   // create
Taxonomy::createOrUpdate($attributes);           // create, or update a matching slug+type
Taxonomy::bulkCreate($rows, $chunkSize = 1000);   // insert many at once, no model events
Taxonomy::find($id);
Taxonomy::findMany($ids, $perPage = null, $page = 1);
Taxonomy::findBySlug('smartphones', TaxonomyType::Category);
Taxonomy::findByType(TaxonomyType::Category, $perPage = null, $page = 1);
Taxonomy::findByParent($parentId, $perPage = null, $page = 1);
Taxonomy::search('phone', TaxonomyType::Category, $perPage = null, $page = 1);
Taxonomy::exists('smartphones', TaxonomyType::Category);
Taxonomy::getTypes();                            // Support\Collection

Taxonomy::tree($type = null, $parentId = null);        // nested, one level of children
Taxonomy::flatTree($type = null, $parentId = null);    // flat, each node carries `depth`
Taxonomy::getNestedTree($type = null);                 // fully nested, via the nested set

Taxonomy::getDescendants($taxonomyId);
Taxonomy::getAncestors($taxonomyId);
Taxonomy::moveToParent($taxonomyId, $parentId);
Taxonomy::rebuildNestedSet($type);
Taxonomy::clearCacheForType($type);
```

> The facade is **not** an Eloquent builder. `Taxonomy::where(...)` and friends do not exist on it. For query building, import the model instead:
>
> ```
> use Aliziodev\LaravelTaxonomy\Models\Taxonomy as TaxonomyModel;
>
> TaxonomyModel::type(TaxonomyType::Category)->ordered()->get();
> ```

Model scopes: `type()`, `root()`, `ordered()`, `roots()`, `atDepth()`, `nestedSetOrder()`.

Bulk imports
------------

[](#bulk-imports)

`create()` costs four to seven queries per row — a slug check, a parent or `max(rgt)` lookup, the range updates that widen the nested set, the insert, and a cache bump. Fine for a handful of rows, painful for a seeder.

`bulkCreate()` resolves slugs in memory, inserts in chunks, and renumbers the nested set once at the end:

```
Taxonomy::bulkCreate([
    ['name' => 'Electronics', 'type' => TaxonomyType::Category],
    ['name' => 'Books',       'type' => TaxonomyType::Category, 'sort_order' => 2],
    ['name' => 'Fiction',     'type' => TaxonomyType::Category, 'parent_id' => $booksId],
]);
```

Measured on 10,000 rows:

TimeQueries`create()` in a loop, flat14.8s40,000**`bulkCreate()`, flat****0.95s****32**`create()` in a loop, nested22.0s59,980**`bulkCreate()`, nested****1.0s****37**Both paths produce the same tree; only the number of round trips differs.

It accepts any iterable, so a generator keeps memory flat on large imports:

```
Taxonomy::bulkCreate((function () {
    foreach (LazyCollection::make($csvRows) as $row) {
        yield ['name' => $row['name'], 'type' => 'category'];
    }
})(), chunkSize: 500);
```

Rows take `name` and `type` (required), plus optional `slug`, `description`, `parent_id`, `sort_order`, `meta`, `created_at` and `updated_at`. Slugs are generated and de-duplicated against both the batch and the rows already in the table; an explicit slug that is already taken raises `DuplicateSlugException`, exactly as `create()` would.

> **Model events are not fired.** That is the trade for the speed. If you rely on observers, or on anything hooked into `creating`/`created`, keep using `create()`. Everything the package itself does in those hooks — slug generation, nested set values, cache invalidation — `bulkCreate()` does for you.

Attaching to models
-------------------

[](#attaching-to-models)

```
$product->attachTaxonomies([$a->id, $b->id]);   // add, keep existing
$product->syncTaxonomies([$a->id, $b->id]);     // replace all
$product->detachTaxonomies([$a->id]);           // remove some
$product->detachTaxonomies();                   // remove all
$product->toggleTaxonomies([$a->id]);           // flip
```

Each accepts an id, a `Taxonomy`, an array, or any collection:

```
$product->attachTaxonomies($taxonomy);
$product->attachTaxonomies(collect([$a, $b]));
$product->attachTaxonomies(TaxonomyModel::type('category')->pluck('id'));
```

> These take taxonomy **ids or models**, not slugs. Passing a slug string attaches nothing. Resolve it first: `Taxonomy::findBySlug('featured', TaxonomyType::Tag)`.

Reading and checking:

```
$product->taxonomies;                                    // relation
$product->taxonomiesOfType(TaxonomyType::Category);      // Collection
$product->getFirstTaxonomyOfType(TaxonomyType::Category);
$product->getTaxonomyCountByType(TaxonomyType::Tag);

$product->hasTaxonomies([$a->id]);                       // any of
$product->hasAllTaxonomies([$a->id, $b->id]);            // all of
$product->hasTaxonomyType(TaxonomyType::Category);       // any of this type
```

### Type-specific variants

[](#type-specific-variants)

Every attach/detach/sync/toggle has an `*OfType` twin that only touches terms of one type, leaving the rest of the model's taxonomies alone:

```
$product->syncTaxonomiesOfType(TaxonomyType::Category, [$catA->id]);   // tags untouched
$product->attachTaxonomiesOfType(TaxonomyType::Tag, [$tagA->id]);
$product->detachTaxonomiesOfType(TaxonomyType::Tag);                   // all tags
$product->toggleTaxonomiesOfType(TaxonomyType::Tag, [$tagA->id]);

$product->hasTaxonomiesOfType(TaxonomyType::Tag, [$tagA->id]);
$product->hasAllTaxonomiesOfType(TaxonomyType::Tag, [$tagA->id]);
```

Ids that are not of the given type are skipped — that filtering is the point of these methods.

Query scopes
------------

[](#query-scopes)

```
Product::withTaxonomy($ids)->get();               // has any of
Product::withAnyTaxonomies($ids)->get();          // has any of
Product::withAllTaxonomies($ids)->get();          // has every one
Product::withoutTaxonomies($ids)->get();          // has none of
Product::withTaxonomyType(TaxonomyType::Category)->get();
Product::withTaxonomySlug('smartphones', TaxonomyType::Category)->get();

Product::withAnyTaxonomiesOfType(TaxonomyType::Tag, $ids)->get();
Product::withAllTaxonomiesOfType(TaxonomyType::Tag, $ids)->get();
Product::withoutTaxonomiesOfType(TaxonomyType::Tag, $ids)->get();

Product::withTaxonomyHierarchy($categoryId)->get();          // term + its descendants
Product::withTaxonomyAtDepth(1, TaxonomyType::Category)->get();
Product::orderByTaxonomyType(TaxonomyType::Category, 'asc', 'name')->get();
```

Scopes chain, so combining them is an AND:

```
Product::withTaxonomySlug('smartphones', TaxonomyType::Category)
    ->withAnyTaxonomiesOfType(TaxonomyType::Tag, $featuredIds)
    ->get();
```

`filterByTaxonomies()` takes a keyed array, handy for request filters:

```
Product::filterByTaxonomies([
    'category' => 'smartphones',        // type => slug
    'color'    => ['red', 'blue'],      // OR within the type
    'exclude'  => $discontinuedIds,
])->get();
```

Hierarchies
-----------

[](#hierarchies)

Hierarchy is stored twice: as `parent_id`, and as nested-set `lft`/`rgt`/`depth` columns kept in sync automatically on create, update, delete and restore.

```
$node->parent;              // relation
$node->children;            // relation, ordered by sort_order
$node->ancestors();         // walks parent_id — correct even if lft/rgt drifted
$node->descendants();       // depth-first, one query per level
$node->getAncestors();      // nested set, single query
$node->getDescendants();    // nested set, single query
$node->getSiblings();
$node->getChildren();

$node->isAncestorOf($other);
$node->isDescendantOf($other);
$node->getLevel();          // depth, 0 for roots

$node->path;                // "Electronics > Smartphones"
$node->full_slug;           // "electronics/smartphones"

$node->moveToParent($newParentId);   // throws on a circular move
```

`getAncestors()`/`getDescendants()` read `lft`/`rgt` and are the fastest option. `ancestors()`/`descendants()` follow `parent_id` and stay correct even if the nested set has drifted — use them if you write to the table outside the model.

> **Refresh before using the nested-set variants on a model you already held.** Adding a child widens its parent's `rgt` in the database, and an instance loaded before that still carries the old bounds — `getDescendants()` then returns an empty collection with no error:
>
> ```
> $parent = Taxonomy::create(['name' => 'Electronics', 'type' => 'category']);
> Taxonomy::create(['name' => 'Phones', 'type' => 'category', 'parent_id' => $parent->id]);
>
> $parent->getDescendants();            // empty — $parent->rgt is stale
> $parent->refresh()->getDescendants(); // 1
> ```
>
>
>
> `descendants()` is keyed on the id, so it is immune to this.

Trees:

```
Taxonomy::tree(TaxonomyType::Category);          // roots with one level eager-loaded
Taxonomy::flatTree(TaxonomyType::Category);      // flat list, `depth` set on each node
Taxonomy::getNestedTree(TaxonomyType::Category); // full depth, `children_nested` + `tree_depth`
```

Types
-----

[](#types)

`TaxonomyType` ships `Category`, `Tag`, `Color`, `Size`, `Unit`, `Type`, `Brand`, `Model`, `Variant`. Everywhere a type is accepted you may pass the enum or a plain string, so custom types need no registration:

```
Taxonomy::create(['name' => 'Winter', 'type' => 'season']);
Product::withTaxonomyType('season')->get();
```

List them in config so tooling and the rebuild command know about them:

```
'types' => ['category', 'tag', 'season', 'department'],
```

Enum helpers:

```
TaxonomyType::values();               // ['category', 'tag', ...]
TaxonomyType::options();              // [['value' => ..., 'label' => ...], ...]
TaxonomyType::Category->label();      // 'Category'
TaxonomyType::Category->getLabel();   // same, named for Filament's HasLabel contract
```

Metadata
--------

[](#metadata)

`meta` is a JSON column, cast to array:

```
Taxonomy::create([
    'name' => 'Electronics',
    'type' => TaxonomyType::Category,
    'meta' => ['icon' => 'devices', 'color' => '#3498db', 'featured' => true],
]);

$taxonomy->meta['icon'];

TaxonomyModel::where('meta->featured', true)->get();
TaxonomyModel::whereJsonContains('meta->tags', 'sale')->get();
```

There is no translation layer; `meta` is a reasonable home for translations:

```
'meta' => ['translations' => ['id' => ['name' => 'Elektronik']]],
```

Caching
-------

[](#caching)

`tree()`, `flatTree()` and `getNestedTree()` are cached for `cache.ttl` (24 hours by default) and invalidated automatically whenever a taxonomy is created, updated, deleted, restored, moved, or rebuilt.

```
Taxonomy::clearCacheForType(TaxonomyType::Category);   // manual, rarely needed
```

Invalidation works by bumping a version key, so entries expire logically rather than being enumerated and deleted — that keeps it correct on cache stores without tag support.

> Do not wrap these calls in another `Cache::remember()`. A second, unversioned layer will not see the package's invalidation and will serve stale trees.

Multi-tenancy
-------------

[](#multi-tenancy)

Two things need attention.

**1. Isolate the cache.** Cache keys are global unless you say otherwise, so without a scope one tenant can be served another tenant's tree. Register a resolver:

```
use Aliziodev\LaravelTaxonomy\TaxonomyManager;

// AppServiceProvider::boot()
TaxonomyManager::resolveCacheScopeUsing(fn () => tenant()?->getKey());
```

Or point `taxonomy.cache.scope` at an invokable class — a class name rather than a closure, so the config survives `php artisan config:cache`:

```
class TenantCacheScope
{
    public function __invoke(): ?string
    {
        return tenant()?->getKey();
    }
}
```

With no scope registered the keys are unchanged from earlier releases, so single-tenant apps need no action.

**2. Scope the data yourself.** The package ships no `tenant_id` column. Add one, and replace the unique index — the shipped `unique(['slug', 'type', 'deleted_at'])` otherwise stops two tenants using the same slug within a type:

```
Schema::table('taxonomies', function (Blueprint $table) {
    $table->dropUnique(['slug', 'type', 'deleted_at']);
    $table->foreignId('tenant_id')->nullable()->index();
    $table->unique(['tenant_id', 'slug', 'type', 'deleted_at']);
});
```

Then point the package at a model carrying your scope:

```
namespace App\Models;

use Aliziodev\LaravelTaxonomy\Models\Taxonomy as BaseTaxonomy;

class Taxonomy extends BaseTaxonomy
{
    protected static function boot(): void
    {
        parent::boot();

        static::addGlobalScope('tenant', function ($query) {
            if ($tenantId = tenant()?->getKey()) {
                $query->where('tenant_id', $tenantId);
            }
        });
    }
}
```

```
'model' => \App\Models\Taxonomy::class,
```

It must extend the package's `Taxonomy` — that is where slug generation and nested-set maintenance live.

> The `*OfType` methods validate the ids you pass **without** applying global scopes, so a taxonomy shared across tenants is not silently discarded. The flip side: an id from another tenant will attach if your application passes it through. Validate user input, e.g. `Rule::exists()` scoped to the tenant.

Slugs and exceptions
--------------------

[](#slugs-and-exceptions)

Slugs are generated from the name and are unique **within a type**, so a `featured` category and a `featured` tag can coexist.

```
Taxonomy::create(['name' => 'Electronics', 'type' => TaxonomyType::Category]);           // 'electronics'
Taxonomy::create(['name' => 'Electronics', 'type' => TaxonomyType::Category]);           // 'electronics-1'
Taxonomy::create(['name' => 'Electronics', 'slug' => 'custom', 'type' => 'category']);   // 'custom'
```

Two exceptions, both extending `TaxonomyException`:

```
use Aliziodev\LaravelTaxonomy\Exceptions\DuplicateSlugException;
use Aliziodev\LaravelTaxonomy\Exceptions\MissingSlugException;

try {
    Taxonomy::create(['name' => 'Electronics', 'slug' => 'taken', 'type' => 'category']);
} catch (DuplicateSlugException $e) {
    $e->getSlug();   // 'taken'
    $e->getType();   // 'category'
}
```

`MissingSlugException` is thrown when `slugs.generate` is `false` and no slug was supplied.

Soft deletes interact with uniqueness through two settings: `consider_trashed` decides whether trashed rows block a slug, and `regenerate_on_restore` decides whether restoring a row with a now-taken slug renames it or throws.

Console commands
----------------

[](#console-commands)

```
php artisan taxonomy:install [--force]
php artisan taxonomy:rebuild-nested-set [type] [--force]
```

`taxonomy:rebuild-nested-set` recomputes `lft`, `rgt` and `depth`. You need it only if rows were written outside the model — direct SQL, a raw seeder, a bulk import. It rebuilds every type when no type is given, uses one transaction per type, and clears the caches afterwards. `--force` is required when running non-interactively.

Examples
--------

[](#examples)

- [E-commerce product catalog](docs/en/ecommerce-product-catalog.md)
- [Content management system](docs/en/content-management-system.md)

Troubleshooting
---------------

[](#troubleshooting)

**Attaching does nothing, no error.** You are probably passing slugs. These methods take ids or models; resolve slugs with `Taxonomy::findBySlug()` first.

**`Call to undefined method ... where()` on the facade.** The facade proxies `TaxonomyManager`, not Eloquent. Import `Models\Taxonomy` for query building.

**Wrong column type on the pivot.** `morph_type` must match your models' keys and is fixed at migration time. Check it before your first `migrate`.

**Tree looks stale.** It should invalidate itself; if you write rows with raw SQL, call `Taxonomy::clearCacheForType()` and, if `lft`/`rgt` are involved, `php artisan taxonomy:rebuild-nested-set`.

**Ancestors or descendants look wrong.** The nested set has drifted, usually from direct SQL writes. Run the rebuild command, or use `ancestors()`/`descendants()`, which follow `parent_id`.

Upgrading
---------

[](#upgrading)

See [UPGRADE.md](UPGRADE.md). Notable in 2.11: cache isolation for multi-tenant apps, and custom relationship names are deprecated for removal in 3.0.

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](CONTRIBUTING.md). Commits follow Conventional Commits; releases and the changelog are generated from them.

```
composer test      # Pest
composer analyse   # PHPStan
composer format    # Pint
```

Security
--------

[](#security)

Report vulnerabilities to  rather than the public tracker.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

56

—

FairBetter than 97% of packages

Maintenance83

Actively maintained with recent releases

Popularity46

Moderate usage in the ecosystem

Community17

Small or concentrated contributor base

Maturity63

Established project with proven stability

 Bus Factor1

Top contributor holds 59.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~15 days

Recently: every ~28 days

Total

28

Last Release

32d ago

Major Versions

v1.0.1 → v2.0.02025-05-31

PHP version history (3 changes)v1.0.0PHP ^8.1

v2.0.0PHP ^8.2

v2.9.0PHP ^8.2|^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/187039973?v=4)[Alizio](/maintainers/aliziodev)[@aliziodev](https://github.com/aliziodev)

---

Top Contributors

[![mu-hanz](https://avatars.githubusercontent.com/u/44943686?v=4)](https://github.com/mu-hanz "mu-hanz (84 commits)")[![aliziodev](https://avatars.githubusercontent.com/u/187039973?v=4)](https://github.com/aliziodev "aliziodev (33 commits)")[![semantic-release-bot](https://avatars.githubusercontent.com/u/32174276?v=4)](https://github.com/semantic-release-bot "semantic-release-bot (23 commits)")[![howdu](https://avatars.githubusercontent.com/u/533658?v=4)](https://github.com/howdu "howdu (2 commits)")

---

Tags

laraveltagscategoriesclosure tablenested-settaxonomyhierarchicaltermstree-structurelaravel-termshierarchical-data

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/aliziodev-laravel-taxonomy/health.svg)

```
[![Health](https://phpackages.com/badges/aliziodev-laravel-taxonomy/health.svg)](https://phpackages.com/packages/aliziodev-laravel-taxonomy)
```

###  Alternatives

[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[laravel/ai

The official AI SDK for Laravel.

1.1k4.6M328](/packages/laravel-ai)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.8k](/packages/tomshaw-electricgrid)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5226.7k](/packages/simplestats-io-laravel-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

265.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
