EasyAdmin 4.28 is out, and it's packed with features to improve your backends.
Clickable Rows in the Index Page
Here's something users have requested for years: clicking anywhere on a row in the index page now navigates to that entity. By default, it goes to the edit page (or falls back to detail if edit isn't available).
But you have full control:
public function configureCrud(Crud $crud): Crud
{
return $crud
// navigate to detail instead of edit
->setDefaultRowAction(Action::DETAIL)
// use a custom action
->setDefaultRowAction('review')
// define a fallback chain (first available action wins)
->setDefaultRowAction([Action::DETAIL, 'preview', Action::EDIT])
// disable row clicks entirely
->setDefaultRowAction(null);
}
The behavior is smart: if the configured action isn't available for a specific entity (disabled, no permission, or condition not met), that row simply won't be clickable. Clicks on checkboxes, buttons, links, or other interactive elements within the row work as expected: they don't trigger navigation.
You can set a global default in your dashboard and override it per CRUD controller:
// in your Dashboard: applies to all CRUD controllers
public function configureCrud(): Crud
{
return Crud::new()->setDefaultRowAction(Action::DETAIL);
}
// in a specific CRUD controller: overrides the dashboard default
public function configureCrud(Crud $crud): Crud
{
return $crud->setDefaultRowAction(Action::EDIT);
}
Read the docs about default row actions
Content Security Policy (CSP) Support
If you've ever tried to use EasyAdmin with strict CSP headers (especially with NelmioSecurityBundle), you know the frustration: script violations everywhere.
Not anymore. EasyAdmin 4.28 automatically injects nonce attributes when the csp_nonce() Twig function is available (i.e. when NelmioSecurityBundle is installed in your application). Inline onclick handlers have been replaced with data attributes processed by JavaScript. Zero configuration required, it just works.
Thanks to Mikołaj Jeziorny for contributing this feature.
Five New Filters
Filtering by country, currency, language, locale, or timezone used to require creating custom filters. Now they're built-in:
$filters
->add(CountryFilter::new('country')->includeOnly(['US', 'CA', 'MX']))
->add(CurrencyFilter::new('currency')->preferredChoices(['USD', 'EUR']))
->add(TimezoneFilter::new('timezone')->forCountryCode('US'));
All filters display localized names using Symfony's Intl component. They support multi-select, preferred choices at the top of the list, and many other options. Read the docs about the new filters.
Confirmation for Any Action
The delete action has always shown a confirmation dialog. Now you can add the same protection to any action with a single method call:
Action::new('archive', 'Archive')
// ...
->askConfirmation();
This displays a generic confirmation modal. But you can customize the message:
Action::new('publish', 'Publish')
// ...
->askConfirmation('Are you sure you want to publish this article?');
Need dynamic content? Use placeholders that get replaced automatically:
->askConfirmation(
'Delete %entity_name% #%entity_id%? This cannot be undone.'
)
You can also customize the confirmation button label (instead of the default "Confirm"):
->askConfirmation(
'Publish this article to production?', 'Yes, publish it'
)
Both parameters support TranslatableInterface objects for full i18n support. And yes, you can now disable confirmation on the delete action if you really want to (though we don't recommend it).
Preferred Choices
When you have dozens of options but users pick the same three 90% of the time, put them at the top:
ChoiceField::new('status')
->setChoices([/* many choices */])
->setPreferredChoices(['draft', 'published']);
The preferred choices appear first, visually separated from the rest.
Read the docs about preferred choices
Autocomplete Customization
Association fields with many options use autocomplete by default. Until now, the dropdown displayed whatever __toString() returned. Now you can customize how entries are formatted.
Using a callback for simple formatting:
AssociationField::new('author')->autocomplete(
callback: static fn (User $user) => sprintf('%s (%s)', $user->getFullName(), $user->getEmail()),
);
Using a Twig template for rich HTML formatting:
AssociationField::new('product')->autocomplete(
template: 'admin/fields/product/autocomplete.html.twig',
renderAsHtml: true,
);
The template receives the entity as the entity variable. Set renderAsHtml: true only when you trust the content (it disables XSS escaping):
{# templates/admin/fields/product/autocomplete.html.twig #}
<div class="product-option">
<strong>{{ entity.name }}</strong>
<span class="text-muted">({{ entity.sku }})</span>
{% if entity.stock < 10 %}
<span class="badge badge-danger">Low Stock</span>
{% endif %}
</div>
Read the docs about autocomplete() customization
✨ If you enjoyed these features and want to see more like it, consider
sponsoring the EasyAdmin project 🙌💡
Top comments (0)