Search by

mrnewport / laravel-nestedterms

mrnewport

A super dynamic & extensible Terms + Tags system for Laravel, featuring infinite nesting, dynamic casting, hierarchical slugs, etc.

Package info

github.com/MrNewport/laravel-nestedterms

pkg:composer/mrnewport/laravel-nestedterms

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 1

v2.0.0 2026-09-13 00:10 UTC

This package is auto-updated.

Last update: 2026-09-19 22:20:42 UTC


README

Current release: 2.0.0. Supported installations: Laravel 12 (PHP 8.2+) and Laravel 13 (PHP 8.3+). CI verifies supported PHP/Laravel combinations. Earlier Laravel versions should remain on the previous major release.

Laravel 12/13 with bounded component dependencies. Fixed structured values being double-encoded, configurable table membership lookups and custom model relations. Parent traversal now rejects cycles and cross-term parents. Modernized the case-sensitive Pest bootstrap and added regression tests.

composer require mrnewport/laravel-nestedterms:^2.0
composer test # from the package checkout; tests use isolated fixtures

GitHub source and tags are published first. Until the release is indexed on Packagist, add this repository as a Composer VCS repository. Never install test dependencies in your production application's require section.

Run php artisan migrate after upgrading to apply package migrations. Back up application data before normal production migrations.

Existing API reference

Laravel Nested Terms & Tags

A dynamic & extensible system for nested Terms and dynamic-cast Tags in Laravel. This package includes:

  • Infinite nesting of Tags via a self-referencing parent_id.
  • Hierarchical slugs (e.g. specifications.bedrooms.5).
  • Dynamic casting of a Tag’s value based on a type field (integer, boolean, float, array, etc.).
  • Polymorphic pivot for attaching Tags to any Eloquent model.
  • Config-driven architecture for easy customization.
  • Comprehensive tests and a production-ready codebase.

Table of Contents

Requirements

  • PHP ^8.1 or higher
  • Laravel ^11.0 (or equivalent)
  • Composer for dependency management

Installation

  1. Install via Composer:

    composer require mrnewport/laravel-nestedterms
  2. Publish the config (optional):

    php artisan vendor:publish --provider="MrNewport\LaravelNestedTerms\Providers\NestedTermsServiceProvider" --tag=nestedterms-config
  3. Migrate:

    php artisan migrate

Configuration

config/nestedterms.php (published if desired):

return [
    'terms_table'     => 'terms',
    'tags_table'      => 'tags',
    'model_tag_table' => 'model_tag',

    'term_model' => \MrNewport\LaravelNestedTerms\Models\Term::class,
    'tag_model'  => \MrNewport\LaravelNestedTerms\Models\Tag::class,

    'allowed_cast_types' => [
        'integer','float','double','boolean','string','array','json','object',
    ],

    'custom_type_map' => [
        'number' => 'integer',
    ],
];
  • Table Names: terms_table, tags_table, model_tag_table
  • Model Classes: term_model & tag_model
  • Casting: 'allowed_cast_types' & 'custom_type_map'

Usage

Creating Terms

use MrNewport\LaravelNestedTerms\Models\Term;

$term = Term::create([
    'name' => 'Specifications',
    'slug' => 'specifications', // or omit to auto-generate
]);

Creating Tags

use MrNewport\LaravelNestedTerms\Models\Tag;

$bedrooms = Tag::create([
    'term_id' => $term->id,
    'name'    => 'Bedrooms',
]);

Infinite Nesting

Tags can nest via parent_id:

$parent = Tag::create([
  'term_id' => $term->id,
  'name'    => 'SubItem',
]);

$child = Tag::create([
  'term_id'   => $term->id,
  'parent_id' => $parent->id,
  'name'      => 'Child Tag',
]);

// slug => "specifications.subitem.child-tag"
$descendants = $parent->allDescendants(); // includes "Child Tag"

Tag Values & Casting

$tag = Tag::create([
    'term_id' => $term->id,
    'name'    => 'NumberOfRooms',
    'type'    => 'integer',
    'value'   => '5',
]);

// Eloquent interprets $tag->value as integer
echo $tag->value;       // 5

Attaching Tags to Models

Any model can “have tags”:

use MrNewport\LaravelNestedTerms\Traits\HasTags;

class Article extends Model
{
    use HasTags;
}
$article->attachTags($tagIdOrSlug);
$article->detachTags($tagInstance);
$article->syncTags([...]);
$article->hasTag('bedrooms');

Filtering by Term

$article->tagsByTerm('specifications')->get();
// returns tags whose term->slug == "specifications"

Customization

Custom Table Names

In nestedterms.php:

'terms_table' => 'cms_terms',
'tags_table'  => 'cms_tags',

Then re-run php artisan migrate. The included migrations reference these config values.

Custom Models

Define your own Term or Tag classes:

namespace App\Models;

use MrNewport\LaravelNestedTerms\Models\Tag as BaseTag;

class CustomTag extends BaseTag
{
    protected $fillable = [
        'term_id','parent_id','slug','type','value','name','description','meta','is_active',
        'icon','color'
    ];

    public function generateHierarchySlug(): string
    {
        $slug = parent::generateHierarchySlug();
        return $slug.'-extended';
    }
}

Then update:

'tag_model' => \App\Models\CustomTag::class,

Testing

A Pest-based suite covers Terms, Tags, and the HasTags trait. Run:

composer test
  • TermTest: verifying creation, slug generation, etc.
  • TagTest: infinite nesting, dynamic casting, hierarchical slugs.
  • HasTagsTraitTest: attaching/detaching tags on a test model.

Contributing

  1. Fork this repo.
  2. Create a feature/fix branch.
  3. Add tests covering changes.
  4. Submit a Pull Request.

License

Licensed under the MIT license.

Enjoy building infinite nesting, dynamic-cast tags, and configurable terms in your Laravel projects!