Skip to content

CRUD Controllers

Edit this page

CRUD controllers provide the CRUD operations (create, show, update, delete) for Doctrine ORM entities. Each CRUD controller can be associated to one or more dashboards.

Technically, these CRUD controllers are regular Symfony controllers so you can do anything you usually do in a controller, such as injecting services and using shortcuts like $this->render() or $this->isGranted().

CRUD controllers must implement the EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\CrudControllerInterface, which ensures that certain methods are defined in the controller. Instead of implementing the interface, you can also extend from the AbstractCrudController class. Run the following command to generate the basic structure of a CRUD controller:

1
$ php bin/console make:admin:crud

CRUD Controller Pages

The four main pages of the CRUD controllers are:

  • index, displays a list of entities which can be paginated, sorted by column and refined with search queries and filters;
  • detail, displays the contents of a given entity;
  • new, allows to create new entity instances;
  • edit, allows to update any property of a given entity.

These pages are generated with four actions with the same name in the AbstractCrudController controller. This controller defines other secondary actions (e.g. delete and autocomplete) which don't match any page.

CRUD Routes

When using pretty admin URLs, each of the CRUD actions define an admin route following this name and path by default:

CRUD route name CRUD route path
*_index /
*_new /new
*_batch_delete /batch-delete
*_autocomplete /autocomplete
*_edit /{entityId}/edit
*_delete /{entityId}/delete
*_detail /{entityId}

For example, for a CRUD controller called ProductCrudController that belongs to a backend with a route named admin and with the path /admin, it will generate the following routes:

Admin route name Admin route path
admin_product_index /admin/product
admin_product_new /admin/product/new
admin_product_batch_delete /admin/product/batch-delete
admin_product_autocomplete /admin/product/autocomplete
admin_product_edit /admin/product/324/edit
admin_product_delete /admin/product/324/delete
admin_product_detail /admin/product/324

Tip

By default, EasyAdmin generates routes for all CRUD controllers on all dashboards. You can restrict which controllers are accessible on each dahboard to not generate all these routes.

You can customize the route names and/or paths of the actions of all the CRUD controllers served by some dashboard using the #[AdminDashboard] attribute:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
// ...

#[AdminDashboard(routes: [
    'index' => ['routePath' => '/all'],
    'new' => ['routePath' => '/create', 'routeName' => 'create'],
    'edit' => ['routePath' => '/editing-{entityId}', 'routeName' => 'editing'],
    'delete' => ['routePath' => '/remove/{entityId}'],
    'detail' => ['routeName' => 'view'],
])]
class SomeDashboardController extends AbstractDashboardController
{
    // ...
}

With this configuration, the routes for the ProductCrudController actions will be:

Admin route name Admin route path
admin_product_index /admin/product/all
admin_product_create /admin/product/create
admin_product_batch_delete /admin/product/current/batch-delete
admin_product_autocomplete /admin/product/current/autocomplete
admin_product_editing /admin/product/current/editing-324
admin_product_delete /admin/product/remove/324
admin_product_view /admin/product/324

You can also customize the path and/or route name of CRUD controllers using the #[AdminCrud] attribute with the following options:

  • routePath: the value that represents the controller in the entire route path (e.g. a /foo path will result in /admin + /foo + /<action>);
  • routeName: the value that represents the controller in the full route name (e.g. a foo_bar route name will result in admin_ + foo_bar + _<action>).

Using the same example as above, you can configure the route names and paths of the controller as follows:

1
2
3
4
5
6
7
8
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminCrud;
// ...

#[AdminCrud(routePath: '/stock/current', routeName: 'stock')]
class ProductCrudController extends AbstractCrudController
{
    // ...
}

The route names/paths will no longer be admin_product_* and /admin/product/* but the following:

Admin route name Admin route path
admin_stock_index /admin/stock/current
admin_stock_new /admin/stock/current/new
admin_stock_batch_delete /admin/stock/current/batch-delete
admin_stock_autocomplete /admin/stock/current/autocomplete
admin_stock_edit /admin/stock/current/324/edit
admin_stock_delete /admin/stock/current/324/delete
admin_stock_detail /admin/stock/current/324

Finally, you can also customize the route name and/or path of each CRUD controller action using the #[AdminAction] attribute:

1
2
3
4
5
6
7
8
9
10
11
12
13
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminAction;
// ...

class ProductCrudController extends AbstractCrudController
{
    // ...

    #[AdminAction(routePath: '/latest-products', routeName: 'latest')]
    public function index(AdminContext $context)
    {
        // ...
    }
}

The index() action of this controller will no longer use the admin_product_index route name and the /admin/product path in the URL. Instead, the route name will be admin_product_latest and the path will be /admin/product/latest-products.

Tip

You can combine the #[AdminDashboard], #[AdminCrud], and #[AdminAction] attributes to customize some or all route names and paths.

Page Names and Constants

Some methods require as argument the name of some CRUD page. You can use any of the following strings: 'index', 'detail', 'edit' and 'new'. If you prefer to use constants for these values, use Crud::PAGE_INDEX, Crud::PAGE_DETAIL, Crud::PAGE_EDIT and Crud::PAGE_NEW (they are defined in the EasyCorp\Bundle\EasyAdminBundle\Config\Crud class).

CRUD Controller Configuration

The only mandatory config option of a CRUD controller is the FQCN of the Doctrine entity being managed by the controller. This is defined as a public static method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace App\Controller\Admin;

use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;

class ProductCrudController extends AbstractCrudController
{
    // it must return a FQCN (fully-qualified class name) of a Doctrine ORM entity
    public static function getEntityFqcn(): string
    {
        return Product::class;
    }

    // ...
}

The rest of CRUD options are configured using the configureCrud() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;

class ProductCrudController extends AbstractCrudController
{
    // ...

    public function configureCrud(Crud $crud): Crud
    {
        return $crud
            ->setEntityLabelInSingular('...')
            ->setDateFormat('...')
            // ...
        ;
    }
}

Design Options

1
2
3
4
5
6
7
8
9
10
11
12
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // set this option if you prefer the page content to span the entire
        // browser width, instead of the default design which sets a max width
        ->renderContentMaximized()

        // set this option if you prefer the sidebar (which contains the main menu)
        // to be displayed as a narrow column instead of the default expanded design
        ->renderSidebarMinimized()
    ;
}

Entity Options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // the labels used to refer to this entity in titles, buttons, etc.
        ->setEntityLabelInSingular('Product')
        ->setEntityLabelInPlural('Products')

        // in addition to a string, the argument of the singular and plural label methods
        // can be a closure that defines two nullable arguments: entityInstance (which will
        // be null in 'index' and 'new' pages) and the current page name
        ->setEntityLabelInSingular(
            fn (?Product $product, ?string $pageName) => $product ? $product->toString() : 'Product'
        )
        ->setEntityLabelInPlural(function (?Category $category, ?string $pageName) {
            return 'edit' === $pageName ? $category->getLabel() : 'Categories';
        })

        // the Symfony Security permission needed to manage the entity
        // (none by default, so you can manage all instances of the entity)
        ->setEntityPermission('ROLE_EDITOR')
    ;
}

Title and Help Options

By default, the page titles of the index and new pages are based on the entity option values defined with the setEntityLabelInSingular() and setEntityLabelInPlural() methods. In the detail and edit pages, EasyAdmin tries first to convert the entity into a string representation and falls back to a generic title otherwise.

You can override the default page titles with the following methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // the visible title at the top of the page and the content of the <title> element
        // it can include these placeholders:
        //   %entity_name%, %entity_as_string%,
        //   %entity_id%, %entity_short_id%
        //   %entity_label_singular%, %entity_label_plural%
        ->setPageTitle('index', '%entity_label_plural% listing')

        // you can pass a PHP closure as the value of the title
        ->setPageTitle('new', fn () => new \DateTime('now') > new \DateTime('today 13:00') ? 'New dinner' : 'New lunch')

        // in DETAIL and EDIT pages, the closure receives the current entity
        // as the first argument
        ->setPageTitle('detail', fn (Product $product) => (string) $product)
        ->setPageTitle('edit', fn (Category $category) => sprintf('Editing <b>%s</b>', $category->getName()))

        // the help message displayed to end users (it can contain HTML tags)
        ->setHelp('edit', '...')
    ;
}

EasyAdmin applies the raw filter to all titles, labels, help messages, etc. displayed in templates. This is done to allow you to customize everything with HTML tags (because those tags will be rendered instead of escaped).

That's why the default page titles used by EasyAdmin only include safe contents like the entity name and ID. Otherwise, your backend could be vulnerable to XSS attacks.

If you change the default page title to include the placeholder %entity_as_string%, check that you don't include user-created contents in the value returned by the __toString() method of the related entity. If you can't avoid that, make sure to sanitize any user submitted data with the Symfony HtmlSanitizer component.

Date, Time and Number Formatting Options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // the argument must be either one of these strings: 'short', 'medium', 'long', 'full', 'none'
        // (the strings are also available as \EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField::FORMAT_* constants)
        // or a valid ICU Datetime Pattern (see https://unicode-org.github.io/icu/userguide/format_parse/datetime/)
        ->setDateFormat('...')
        ->setTimeFormat('...')

        // first argument = datetime pattern or date format; second optional argument = time format
        ->setDateTimeFormat('...', '...')

        ->setDateIntervalFormat('%%y Year(s) %%m Month(s) %%d Day(s)')
        ->setTimezone('...')

        // this option makes numeric values to be rendered with a sprintf()
        // call using this value as the first argument.
        // this option overrides any formatting option for all numeric values
        // (e.g. setNumDecimals(), setRoundingMode(), etc. are ignored)
        // NumberField and IntegerField can override this value with their
        // own setNumberFormat() methods, which works in the same way
        ->setNumberFormat('%.2d')

        // Sets the character used to separate each thousand group in a number
        // e.g. if separator is ',' then 12345 is formatted as 12,345
        // By default, EasyAdmin doesn't add any thousands separator to numbers;
        // NumberField and IntegerField can override this value with their
        // own setThousandsSeparator() methods, which works in the same way
        ->setThousandsSeparator(',')

        // Sets the character used to separate the decimal part of a non-integer number
        // e.g. if separator is '.' then 1/10 is formatted as 0.1
        // by default, EasyAdmin displays the default decimal separator used by PHP;
        // NumberField and IntegerField can override this value with their
        // own setDecimalSeparator() methods, which works in the same way
        ->setDecimalSeparator('.')
    ;
}

Search, Order, and Pagination Options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // ...

        // the names of the Doctrine entity properties where the search is made on
        // (by default it looks for in all properties)
        ->setSearchFields(['name', 'description'])
        // use dots (e.g. 'seller.email') to search in Doctrine associations
        ->setSearchFields(['name', 'description', 'seller.email', 'seller.address.zipCode'])
        // set it to null to disable and hide the search box
        ->setSearchFields(null)
        // call this method to focus the search input automatically when loading the 'index' page
        ->setAutofocusSearch()

        // by default, the search results match all the terms (SearchMode::ALL_TERMS):
        // term1 in (field1 or field2) AND term2 in (field1 or field2)
        // e.g. if you look for 'lorem ipsum' in [title, description],
        // results require matching 'lorem' in either title or description
        // (or both) AND 'ipsum' in either title or description (or both)
        ->setSearchMode(SearchMode::ALL_TERMS)

        // use the SearchMode::ANY_TERMS option to change the search mode to
        // match at least one of the terms:
        // term1 in (field1 or field2) OR term2 in (field1 or field2)
        // e.g. if you look for 'lorem ipsum' in [title, description],
        // results will match either 'lorem' in title or description (or both)
        // OR 'ipsum' in title or description (or both)
        ->setSearchMode(SearchMode::ANY_TERMS)
    ;
}

Tip

The search engine splits all terms by default (searching for foo bar returns items with foo and bar). You can wrap all or part of your query with quotes to make an exact search: "foo bar" only returns items with that exact content, including the middle white space.

1
2
3
4
5
6
7
8
9
10
11
12
13
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // ...

        // defines the initial sorting applied to the list of entities
        // (user can later change this sorting by clicking on the table columns)
        ->setDefaultSort(['id' => 'DESC'])
        ->setDefaultSort(['id' => 'DESC', 'title' => 'ASC', 'startsAt' => 'DESC'])
        // you can sort by Doctrine associations up to two levels
        ->setDefaultSort(['seller.name' => 'ASC'])
    ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // ...

        // the max number of entities to display per page
        ->setPaginatorPageSize(30)
        // the number of pages to display on each side of the current page
        // e.g. if num pages = 35, current page = 7 and you set ->setPaginatorRangeSize(4)
        // the paginator displays: [Previous]  1 ... 3  4  5  6  [7]  8  9  10  11 ... 35  [Next]
        // set this number to 0 to display a simple "< Previous | Next >" pager
        ->setPaginatorRangeSize(4)

        // these are advanced options related to Doctrine Pagination
        // (see https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/pagination.html)
        ->setPaginatorUseOutputWalkers(true)
        ->setPaginatorFetchJoinCollection(true)
    ;
}

Note

When using Doctrine filters, listings may not include some items because they were removed by those global Doctrine filters. Use the dashboard route name to not apply the filters when the request URL belongs to the dashboard You can also get the dashboard route name via the application context variable.

The default Doctrine query executed to get the list of entities displayed in the index page takes into account the sorting configuration, the optional search query, the optional filters and the pagination. If you need to fully customize this query, override the createIndexQueryBuilder() method in your CRUD controller.

Templates and Form Options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // this method allows to use your own template to render a certain part
        // of the backend instead of using EasyAdmin default template
        // the first argument is the "template name", which is the same as the
        // Twig path but without the `@EasyAdmin/` prefix and the `.html.twig` suffix
        ->overrideTemplate('crud/field/id', 'admin/fields/my_id.html.twig')

        // the theme/themes to use when rendering the forms of this entity
        // (in addition to EasyAdmin default theme)
        ->addFormTheme('foo.html.twig')
        // this method overrides all existing form themes (including the
        // default EasyAdmin form theme)
        ->setFormThemes(['my_theme.html.twig', 'admin.html.twig'])

        // this sets the options of the entire form (later, you can set the options
        // of each form type via the methods of their associated fields)
        // pass a single array argument to apply the same options for the new and edit forms
        ->setFormOptions([
            'validation_groups' => ['Default', 'my_validation_group']
        ]);

        // pass two array arguments to apply different options for the new and edit forms
        // (pass an empty array argument if you want to apply no options to some form)
        ->setFormOptions(
            ['validation_groups' => ['my_validation_group']],
            ['validation_groups' => ['Default'], '...' => '...'],
        );
    ;
}

Other Options

1
2
3
4
5
6
7
8
9
10
11
public function configureCrud(Crud $crud): Crud
{
    return $crud
        // by default, when the value of some field is `null`, EasyAdmin displays
        // a label with the `null` text. You can change that by overriding
        // the `label/null` template. However, if you have lots of `null` values
        // and want to reduce the "visual noise" in your backend, you can use
        // the following option to not display anything when some value is `null`
        // (this option is applied both in the `index` and `detail` pages)
        ->hideNullValues()
}

Custom Redirect After Creating or Editing Entities

By default, when clicking on "Save" button when creating or editing entities you are redirected to the previous page. If you want to change this behavior, override the getRedirectResponseAfterSave() method.

For example, if you've added a custom action called "Save and view detail", you may prefer to redirect to the detail page after saving the changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected function getRedirectResponseAfterSave(AdminContext $context, string $action): RedirectResponse
{
    $submitButtonName = $context->getRequest()->request->all()['ea']['newForm']['btn'];

    if ('saveAndViewDetail' === $submitButtonName) {
        // when using pretty admin URLs
        return $this->redirectToRoute('admin_product_detail', [
            'entityId' => $context->getEntity()->getPrimaryKeyValue(),
        ]);

        // when using legacy admin URLs
        $url = $this->container->get(AdminUrlGenerator::class)
            ->setAction(Action::DETAIL)
            ->setEntityId($context->getEntity()->getPrimaryKeyValue())
            ->generateUrl();

        return $this->redirect($url);
    }

    return parent::getRedirectResponseAfterSave($context, $action);
}

Same Configuration in Different CRUD Controllers

If you want to do the same config in all CRUD controllers, there's no need to repeat the config in each controller. Instead, add the configureCrud() method in your dashboard and all controllers will inherit that configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;

class DashboardController extends AbstractDashboardController
{
    // ...

    public function configureCrud(): Crud
    {
        return Crud::new()
            // this defines the pagination size for all CRUD controllers
            // (each CRUD controller can override this value if needed)
            ->setPaginatorPageSize(30)
        ;
    }
}

Fields

Fields allow to display the contents of your Doctrine entities on each CRUD page. EasyAdmin provides built-in fields to display all the common data types, but you can also create your own fields.

If your CRUD controller extends from the AbstractCrudController, the fields are configured automatically. In the index page you'll see a few fields and in the rest of pages you'll see as many fields as needed to display all the properties of your Doctrine entity.

Read the chapter about Fields to learn how to configure which fields to display on each page, how to configure the way each field is rendered, etc.

Customizing CRUD Actions

The default CRUD actions (index(), detail(), edit(), new() and delete() methods in the controller) implement the most common behaviors used in applications.

The first way to customize their behavior is to override those methods in your own controllers. However, the original actions are so generic that they contain quite a lot of code, so overriding them is not that convenient.

Instead, you can override other smaller methods that implement certain features needed by the CRUD actions. For example, the index() action calls to a method named createIndexQueryBuilder() to create the Doctrine query builder used to get the results displayed on the index listing. If you want to customize that listing, it's better to override the createIndexQueryBuilder() method instead of the entire index() method. There are many of these methods, so you should check the EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController class.

The other alternative to customize CRUD actions is to use the events triggered by EasyAdmin, such as BeforeCrudActionEvent and AfterCrudActionEvent.

Creating, Persisting and Deleting Entities

Most of the actions of a CRUD controller end up creating, persisting or deleting entities. If your CRUD controller extends from the AbstractCrudController, these methods are already implemented, but you can customize them overriding methods and listening to events.

First, you can override the createEntity(), updateEntity(), persistEntity() and deleteEntity() methods. The createEntity() method for example only executes return new $entityFqcn(), so you need to override it if your entity needs to pass constructor arguments or set some of its properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace App\Controller\Admin;

use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;

class ProductCrudController extends AbstractCrudController
{
    public static function getEntityFqcn(): string
    {
        return Product::class;
    }

    public function createEntity(string $entityFqcn)
    {
        $product = new Product();
        $product->createdBy($this->getUser());

        return $product;
    }

    // ...
}

The other way of overriding this behavior is listening to the events triggered by EasyAdmin when an entity is created, updated, persisted, deleted, etc.

Passing Additional Variables to CRUD Templates

The default CRUD actions implemented in AbstractCrudController don't end with the usual $this->render('...') instruction to render a Twig template and return its contents in a Symfony Response object.

Instead, CRUD actions return a EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore object with the variables passed to the template that renders the CRUD action contents. This KeyValueStore object is similar to Symfony's ParameterBag object. It's like an object-oriented array with useful methods such as get(), set(), has(), etc.

Before ending each CRUD action, their KeyValueStore object is passed to a method called configureResponseParameters() which you can override in your own controller to add/remove/change those template variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace App\Controller\Admin;

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

class ProductCrudController extends AbstractCrudController
{
    // ...

    public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore
    {
        if (Crud::PAGE_DETAIL === $responseParameters->get('pageName')) {
            $responseParameters->set('foo', '...');

            // keys support the "dot notation", so you can get/set nested
            // values separating their parts with a dot:
            $responseParameters->setIfNotSet('bar.foo', '...');
            // this is equivalent to: $parameters['bar']['foo'] = '...'
        }

        return $responseParameters;
    }
}

You can add as many or as few parameters to this KeyValueStore object as you need. The only mandatory parameter is either templateName or templatePath to set respectively the name or path of the template to render as the result of the CRUD action.

Template Names and Template Paths

All the templates used by EasyAdmin to render its contents are configurable. That's why EasyAdmin deals with "template names" instead of normal Twig template paths.

A template name is the same as the template path but without the @EasyAdmin prefix and the .html.twig suffix. For example, @EasyAdmin/layout.html.twig refers to the built-in layout template provided by EasyAdmin. However, layout refers to "whichever template is configured as the layout in the application".

Working with template names instead of paths gives you full flexibility to customize the application behavior while keeping all the customized templates. In Twig templates, use the ea.templatePath() function to get the Twig path associated to the given template name:

1
2
3
4
5
6
7
<div id="flash-messages">
    {{ include(ea.templatePath('flash_messages')) }}
</div>

{% if some_value is null %}
    {{ include(ea.templatePath('label/null')) }}
{% endif %}

Generating Admin URLs

When using pretty admin URLs, EasyAdmin generates one route per each CRUD action of each dashboard. You can list them all with the following command:

1
$ php bin/console debug:router

If you don't see some or any of your admin routes, clear the cache of your Symfony application so the EasyAdmin route loader can generate them again:

1
$ php bin/console cache:clear

You can use any of these routes to generate the admin URLs thanks to the utilities provided by Symfony to generate URLs:

1
2
3
4
5
6
7
8
9
10
// redirecting to an admin URL inside a controller
return $this->redirectToRoute('admin_product_new');

// generating an admin URL inside a service
$userProfileUrl = $this->router->generate('admin_user_detail', [
    'entityId' => $user->getId(),
]);

// generating an admin URL in a Twig template
<a href="{{ path('admin_blog_post_edit', {entityId: post.id}) }}">Edit Blog Post</a>

Building Admin URLs

If you don't use pretty admin URLs or if you need to build routes dynamically, you can use the AdminUrlGenerator provided by EasyAdmin to build the admin URLs.

When generating a URL this way, you don't start from scratch. EasyAdmin reuses all the query parameters existing in the current request. This is done on purpose because generating new URLs based on the current URL is the most common scenario. Use the unsetAll() method to remove all existing query parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;

class SomeCrudController extends AbstractCrudController
{
    private $adminUrlGenerator;

    public function __construct(AdminUrlGenerator $adminUrlGenerator)
    {
        $this->adminUrlGenerator = $adminUrlGenerator;
    }

    // ...

    public function someMethod()
    {
        // instead of injecting the AdminUrlGenerator service in the constructor,
        // you can also get it from inside a controller action as follows:
        // $adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);

        // the existing query parameters are maintained, so you only
        // have to pass the values you want to change.
        $url = $this->adminUrlGenerator->set('page', 2)->generateUrl();

        // you can remove existing parameters
        $url = $this->adminUrlGenerator->unset('menuIndex')->generateUrl();
        $url = $this->adminUrlGenerator->unsetAll()->set('foo', 'someValue')->generateUrl();

        // the URL builder provides shortcuts for the most common parameters
        $url = $this->adminUrlGenerator
            ->setController(SomeCrudController::class)
            ->setAction('theActionName')
            ->generateUrl();

        // ...
    }
}

Tip

If you need to deal with the admin URLs manually for any reason, the names of the query string parameters are defined as constants in the EA class.

The exact same features are available in templates thanks to the ea_url() Twig function. In templates you can omit the call to the generateUrl() method (it will be called automatically for you):

1
2
3
4
5
6
7
8
9
{# both are equivalent #}
{% set url = ea_url({ page: 2 }).generateUrl() %}
{% set url = ea_url({ page: 2 }) %}

{% set url = ea_url().set('page', 2) %}

{% set url = ea_url()
    .setController('App\\Controller\\Admin\\SomeCrudController')
    .setAction('theActionName') %}

Generating CRUD URLs from outside EasyAdmin

When generating URLs of EasyAdmin pages from outside EasyAdmin (e.g. from a regular Symfony controller) the admin context variable is not available. That's why you must always set the CRUD controller associated to the URL. If you have more than one dashboard, you must also set the Dashboard:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
use App\Controller\Admin\DashboardController;
use App\Controller\Admin\ProductCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class SomeSymfonyController extends AbstractController
{
    private $adminUrlGenerator;

    public function __construct(AdminUrlGenerator $adminUrlGenerator)
    {
        $this->adminUrlGenerator = $adminUrlGenerator;
    }

    public function someMethod()
    {
        // if your application only contains one Dashboard, it's enough
        // to define the controller related to this URL
        $url = $this->adminUrlGenerator
            ->setController(ProductCrudController::class)
            ->setAction(Action::INDEX)
            ->generateUrl();

        // in applications containing more than one Dashboard, you must also
        // define the Dashboard associated to the URL
        $url = $this->adminUrlGenerator
            ->setDashboard(DashboardController::class)
            ->setController(ProductCrudController::class)
            ->setAction(Action::INDEX)
            ->generateUrl();

        // some actions may require to pass additional parameters
        $url = $this->adminUrlGenerator
            ->setController(ProductCrudController::class)
            ->setAction(Action::EDIT)
            ->setEntityId($product->getId())
            ->generateUrl();

        // ...
    }
}

The same applies to URLs generated in Twig templates:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{# if your application defines only one Dashboard #}
{% set url = ea_url()
    .setController('App\\Controller\\Admin\\ProductCrudController')
    .setAction('index') %}
{# if you prefer PHP constants, use this:
   .setAction(constant('EasyCorp\\Bundle\\EasyAdminBundle\\Config\\Action::INDEX')) #}

{# if your application defines multiple Dashboards #}
{% set url = ea_url()
    .setDashboard('App\\Controller\\Admin\\DashboardController')
    .setController('App\\Controller\\Admin\\ProductCrudController')
    .setAction('index') %}

{# some actions may require to pass additional parameters #}
{% set url = ea_url()
    .setController('App\\Controller\\Admin\\ProductCrudController')
    .setAction('edit')
    .setEntityId(product.id) %}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version