nivas/easyadmin-tree-list-bundle

Plugin for EasyAdmin to show nested tree in list view

Maintainers

Package info

github.com/nivas/easyadmin-tree-list-bundle

Language:JavaScript

Type:symfony-bundle

pkg:composer/nivas/easyadmin-tree-list-bundle

Transparency log

Statistics

Installs: 603

Dependents: 0

Suggesters: 0

Stars: 8

Open Issues: 0

v3.0.0 2026-08-25 13:59 UTC

This package is auto-updated.

Last update: 2026-08-25 14:01:30 UTC


README

EasyAdmin 4.x / Symfony 6.4+ & 7.x compatible bundle which overrides the default EasyAdmin index (list) template and adds a nested tree view on list for entities that use the Gedmo Tree extension.

EasyAdmin tree list bundle demo

Version compatibility

Bundle version EasyAdmin Symfony PHP
v3.x 4.x 6.4+ / 7.x >= 8.1
v2.x 4.x 6.4+ / 7.x >= 8.1
v1.0.0 3.x 5.x >= 7.2

In order to achieve the nested tree view, your entity must implement a Gedmo nested tree with the following properties:

  • root association
  • parent association
  • lft (left) property
  • rgt (right) property
  • lvl (level) property

Example of such an entity: https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/tree.md

Since v2, the template reads the tree structure directly from the entity's root and parent associations (rendered as data-root-id / data-parent-id row attributes via EasyAdmin 4's entity_row_attributes block hook). The lft property is still used for ordering the index query so rows arrive in tree order.

Notable mentions and some history

On many Symfony 4 projects, we used 2lenet/EasyAdminPlusBundle's tree view feature. As we migrated projects to Symfony v5 / EasyAdmin v3 and later Symfony v7 / EasyAdmin v4 - we lacked this simple tree view feature and decided to make tree view work again.

History:

To achieve tree functionality in templates, the really old jQuery treetable Plugin 3.2.0 was used from Ludo van den Boom, just like in the 2lenet/EasyAdminPlusBundle bundle. Will check out how to replace it with something recent.

Installation

composer require nivas/easyadmin-tree-list-bundle

after installation your bundles.php will contain the new bundle:

<?php

return [
...
    Nivas\Bundle\EasyAdminTreeListBundle\EasyAdminTreeListBundle::class => ['all' => true],
];

Configuration

As mentioned previously, your entity must implement Gedmo Tree.

Your EasyAdmin CRUD controller should override:

  • configureResponseParameters - used to enable tree rendering for the entity's index template
  • createIndexQueryBuilder - used to change sorting order needed to make a tree list
  • configureActions - (optional) used to remove batch actions in front of the tree navigation arrow

Example of src/Controller/Admin/TermCrudController.php:

<?php

namespace App\Controller\Admin;

use App\Entity\Term;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;

// for the custom order query
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use Doctrine\ORM\QueryBuilder;

// for disabling batch actions
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;

class TermCrudController extends AbstractCrudController
{
    private EntityRepository $entityRepository;

    public function __construct(EntityRepository $entityRepository)
    {
        $this->entityRepository = $entityRepository;
    }

    public static function getEntityFqcn(): string
    {
        return Term::class;
    }

    // REQUIRED - enables the tree template override, replacement for the old yaml config "tree: true"
    public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore
    {
        $responseParameters->set('tree', true);
        return $responseParameters;
    }

    // REQUIRED - without this the list is not sorted in tree order and the tree is not assembled correctly
    public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
    {
        $qb = $this->entityRepository->createQueryBuilder($searchDto, $entityDto, $fields, $filters);
        $qb->resetDQLPart('orderBy');
        $qb->addOrderBy($qb->getRootAlias().'.root', 'ASC');
        $qb->addOrderBy($qb->getRootAlias().'.lft', 'ASC');
        return $qb;
    }

    // not needed, the tree list just looks better without batch actions
    public function configureActions(Actions $actions): Actions
    {
        return $actions
            ->disable(Action::BATCH_DELETE);
    }

  ...

}

Server-side lazy loading (v3)

By default the whole tree is rendered in one page, which gets slow and memory-hungry for large trees (EasyAdmin's per-row rendering cost dominates — a ~700-row tree costs hundreds of MB with the Symfony profiler enabled). v3 adds an opt-in lazy mode:

  • the initial page renders only root nodes; rows whose subtree is non-empty (nested set: rgt - lft > 1) get an expander arrow via data-branch="true",
  • expanding an unloaded node fetches the same index URL with ?treeParent=<id> and inserts the returned rows into the tree (treetable('loadBranch')),
  • searching still spans the whole tree (matched rows whose parents are missing from the result set are shown top-level, as before).

Enable it by (1) setting the tree_lazy response parameter and (2) filtering your index query builder — the template does the rest:

    public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore
    {
        $responseParameters->set('tree', true);
        $responseParameters->set('tree_lazy', true); // NEW in v3
        return $responseParameters;
    }

    public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
    {
        $qb = $this->entityRepository->createQueryBuilder($searchDto, $entityDto, $fields, $filters);
        $qb->resetDQLPart('orderBy');
        $root = $qb->getRootAlias();

        // lazy tree: initially only roots; expanding a node fetches ?treeParent=<id>.
        // Search keeps querying the whole tree.
        $treeParent = $this->requestStack->getCurrentRequest()?->query->getInt('treeParent') ?: null;
        if ($treeParent !== null) {
            $qb->andWhere($root.'.parent = :treeParent')->setParameter('treeParent', $treeParent);
        } elseif (!$searchDto->getQuery()) {
            $qb->andWhere($root.'.lvl = 0');
        }

        $qb->addOrderBy($root.'.root', 'ASC');
        $qb->addOrderBy($root.'.lft', 'ASC');
        return $qb;
    }

($this->requestStack is a constructor-injected Symfony\Component\HttpFoundation\RequestStack.)

Make sure the page size covers your widest node ($crud->setPaginatorPageSize(...)) — each treeParent fetch returns ALL children of that node on one page.

v2 to v3 migration

  • Non-lazy setups: no changes needed — without tree_lazy the template behaves exactly like v2 (the whole tree renders in one page, expanders work client-side).
  • The bundle's DI extension no longer triggers the Symfony 7.1+ deprecations (internal HttpKernel\DependencyInjection\Extension and untyped prepend()).

v1 to v2 migration

v2 targets EasyAdmin 4 only (use v1.0.0 for EasyAdmin 3). Notable changes:

  • The index template no longer copies the whole EasyAdmin template - it extends @!EasyAdmin/crud/index.html.twig and only overrides the entity_row_attributes and body_javascript blocks, so it survives EasyAdmin template changes much better.
  • Parent/child relations are no longer reconstructed in Twig from lft/rgt/lvl values - each row gets data-root-id/data-parent-id directly from the entity's root/parent associations.
  • Filtered or searched result sets no longer break the view: rows whose parent is not present in the current result set drop their data-parent-id and are shown as top-level rows instead of being hidden by jquery.treetable.

No configuration changes are needed in your CRUD controllers besides the EasyAdmin 4 EntityRepository constructor injection shown above (the EasyAdmin 3 $this->get(EntityRepository::class) container access was removed in EasyAdmin 4).