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 Ltree Laravel Package

happenv-com/laravel-ltree

Laravel integration for PostgreSQL's ltree extension, making it easy to store and query hierarchical data such as categories, menus, and org trees. Supports path-based ancestor/descendant lookups, subtree moves, and Eloquent model traits for managing tree structures directly in your database.

View on GitHub
Deep Wiki
Context7

Getting Started

This package brings PostgreSQL's ltree extension into Eloquent, giving you materialized-path style hierarchies (categories, org charts, comment threads, menus) with native @> / <@ operators and GiST indexing instead of recursive CTEs.

Minimal path to first value:

  1. Confirm your database. You need PostgreSQL with the ltree contrib extension available on the server. SQLite and MySQL are not supported — full stop. Check with SELECT * FROM pg_available_extensions WHERE name = 'ltree'; before installing anything.
  2. composer require happenv-com/laravel-ltree
  3. Look first at the package's migration stubs and the model trait/concern. Those two files define the entire contract: the column name it expects for the path, how labels are generated, and which query scopes are available. Read them before writing any schema.
  4. Enable the extension and add an ltree column in your own migration, then add the trait to your model.

First use case to validate the integration end-to-end: a categories table where each row stores a dotted path like electronics.phones.android. Create a root, attach two children, then assert that Category::descendantsOf($root) returns the subtree. If that works, everything else is variation on the same theme.

Since v1.0.0 is the first stable tag and ships with only a changelog link, treat the README and test suite as the authoritative API reference and pin the version ("happenv-com/laravel-ltree": "^1.0") in composer.json.

Implementation Patterns

Schema. Enable the extension once per database (CREATE EXTENSION IF NOT EXISTS ltree;) — usually in an early migration, not per-table. The path column should be ltree typed, not text, or you lose operator and index support. Add a GiST index on it in the same migration:

DB::statement('CREATE INDEX categories_path_gist ON categories USING GIST (path);');

Model. Add the package trait to the model and point it at your path column and the attribute used to build labels (typically slug or id). Keep the path column out of $fillable — it should only ever be written by the package, otherwise you get orphaned subtrees.

Creating nodes. Prefer the package's append/child helpers over hand-assigning paths. A hand-rolled $parent->path . '.' . $slug skips the uniqueness and validation logic and breaks the moment two nodes share a slug.

Querying. The two scopes you'll use daily are descendants (path <@ ?) and ancestors (path @> ?). Reach for these instead of whereHas recursion — they're a single index-backed scan. For depth-limited queries, nlevel(path) gives you the depth and composes cleanly into orderBy.

Rendering trees. For menus and select boxes, fetch the subtree in one flat query ordered by path and build the nested array in PHP. One query per level is the anti-pattern this package exists to eliminate.

Moving subtrees. Re-parenting is a single UPDATE ... SET path = new_prefix || subpath(path, nlevel(old_prefix)) touching all descendants at once. Use the package's move helper if it exposes one; if not, wrap the raw statement in a transaction and lock the affected rows (lockForUpdate) — concurrent inserts under the old parent during a move will corrupt the tree.

Backfilling existing data. If you're migrating from parent_id, do it in a maintenance window or behind a feature flag. Order rows by depth (roots first), compute paths in PHP, and write them in bulk — the trait's per-model save path is too slow for large tables.

Gotchas and Tips

  • Label characters are the #1 source of bugs. ltree labels allow alphanumerics and underscores (and hyphens on PG 16+, but not as a leading character). Dots are separators and cannot appear in a label. If your slugs contain dots or unicode, sanitize them or use numeric IDs as labels and keep the human-readable slug in a separate column.
  • Path length limits. ltree paths max out at 65,535 bytes with a limit on label count per path. Deep trees with long slugs will eventually fail on insert. Numeric labels sidestep this entirely.
  • Postgres-only means CI friction. Add a Postgres service container to your test pipeline. Tests that pass locally against a dev Postgres will silently skip or fail on a colleague's SQLite setup — guard those tests explicitly.
  • The GiST index is not optional. Without it, @> and <@ degrade to sequential scans. Confirm it's used with EXPLAIN ANALYZE on a realistic table size before you trust the performance claims.
  • Creating the extension requires superuser or a trusted extension. On managed Postgres (RDS, Supabase, Neon) it's usually allowed, but if your deploy user can't run CREATE EXTENSION, that step belongs in provisioning, not in a migration that runs on every deploy.
  • Concurrency on insert. Two simultaneous children of the same parent can race on max-path lookup. If the package doesn't wrap appends in a transaction with an advisory lock, do it yourself in a small service class.
  • v1.0.0 shipped without detailed release notes — only a changelog link. Before adopting, skim the commit history and the test suite for the actual scope of the API, and check whether the maintainers publish an upgrade guide. First-stable releases in small packages can still reshuffle method names in 1.x.
  • Debugging strategy: log the raw SQL (DB::listen) for any scope that surprises you. The generated ltree expressions are the fastest way to see whether the bug is in your label generation or the query.
  • Extension point to watch: label generation is the seam you'll most likely need to override — multi-tenant prefixes, numeric vs. slug labels, or case-normalising. Subclass the trait or bind a custom implementation rather than patching vendor code.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console