EasyAdmin 5.1 is out with a small new feature that delivers a big quality-of-life improvement: your custom actions can now receive the current entity (e.g. Order $order) as a typed argument, automatically injected by Symfony with zero configuration.
The {entityId} Placeholder
Every EasyAdmin route that operates on a single entity (edit, detail, delete, or your own custom actions) identifies that entity through a route placeholder. Until now, that placeholder was always called {entityId}:
#[AdminRoute('/{entityId}/invoice')]
public function renderInvoice(): Response
{
// ...
}
It works, but entityId is an EasyAdmin-specific name, and Symfony has no way of knowing that it represents your entity's identifier.
Custom Actions: Before and After
Imagine you have an OrderCrudController with a custom action that renders an invoice:
use App\Entity\Order;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class OrderCrudController extends AbstractCrudController
{
public function configureActions(Actions $actions): Actions
{
$viewInvoice = Action::new('viewInvoice', 'Invoice', 'fa fa-file-invoice')
->linkToCrudAction('renderInvoice');
return $actions->add(Crud::PAGE_DETAIL, $viewInvoice);
}
// ...
}
The interesting part is the action method. In most cases, you'll want access to the actual Order object. Before, when using {entityId}, you had two options, and neither was particularly elegant.
Option 1: Explicitly define a mapped route parameter so Symfony knows how to resolve the entity:
#[AdminRoute('/{entityId:order.id}/invoice')]
public function renderInvoice(Order $order): Response
{
// ...
}
Option 2: Receive the raw identifier and retrieve the entity yourself from the admin context:
#[AdminRoute('/{entityId}/invoice')]
public function renderInvoice(AdminContext $context): Response
{
$order = $context->getEntity()->getInstance();
// ...
}
The New {id} Placeholder
EasyAdmin 5.1 lets you use {id} as an alias for {entityId}. This may look like a small change, but it unlocks automatic entity injection and makes route definitions feel more natural. Your custom action can now look like this:
#[AdminRoute('/{id}/invoice')]
public function renderInvoice(Order $order): Response
{
// that's it, $order is the fully loaded entity
}
That's the entire trick. Because the placeholder is named {id} (matching the identifier property used by most entities), Symfony's EntityValueResolver can automatically inject the corresponding Order. No mapping syntax and no manual lookup. Your custom action becomes a regular Symfony controller method.
Note: Automatic entity injection relies on DoctrineBundle's
controller_resolver.auto_mappingoption. If your application doesn't enable it, add a#[MapEntity]attribute to the argument (#[MapEntity] Order $order).
Building Admin URLs: setId()
The same idea applies when building admin URLs programmatically. EasyAdmin's AdminUrlGenerator (available in Twig as ea_url()) has always provided a setEntityId() method:
{% set url = ea_url()
.setController('...')
.setAction('detail')
.setEntityId(product.id) %}
EasyAdmin 5.1 introduces a shorter and more natural alias: setId():
{% set url = ea_url()
.setController('...')
.setAction('detail')
.setId(product.id) %}
<a href="{{ url }}">{{ product.name }}</a>
You can use both methods indistinctevily. Also, setId() works regardless of whether the target route uses {id} or {entityId}. Internally, it stores the canonical entity identifier, and EasyAdmin translates it into the appropriate route parameter when generating the URL.
The New Placeholder Also Works in CRUD Route Paths
{id} is accepted anywhere EasyAdmin expects the entity placeholder, including when customizing the route paths of the built-in edit, detail, and delete actions:
// '/{id}' is equivalent to '/{entityId}', but plays nicer with Symfony
'detail' => ['routePath' => '/{id}']
Summary
{id} is a drop-in alias for {entityId}, so both placeholders continue to work and nothing breaks. There's no need to update existing code. However, switching to {id} gives you cleaner route definitions and automatic entity injection in custom actions, making them feel like regular Symfony controllers. Combined with the new setId() method, it also makes URL generation a little cleaner and more expressive throughout your admin code.
✨ If you enjoyed this feature and want to see more like it, consider sponsoring the EasyAdmin project 🙌💡
Top comments (0)