EasyAdmin lets you create CRUD-based backends with minimal setup: define your entities, add CRUD controllers, get a complete admin interface.
You can extend these CRUD controllers with custom actions (e.g. approve
action for users, duplicate
action for products, etc.) These custom actions must live inside the CRUD controller, which isn't always practical.
Also, your application has features that don't fit the CRUD pattern and require their own controllers with custom logic. You could always embed external controllers in EasyAdmin, but the integration was clunky. The external controllers were integrated via their Symfony routes from your main menu like this:
yield MenuItem::linkToRoute('Business Stats', 'fa fa-chart-bar', 'business_stats_index', [
'param1' => 'value1',
'param2' => 'value2',
]);
But the generated URLs revealed the awkward integration:
/admin?routeName=business_stats_index&routeParams%5Bparam1%5D=value1&routeParams%5Bparam2%5D=value2
The AdminRoute
Attribute
EasyAdmin 4.25.0 introduces the #[AdminRoute]
attribute for seamless Symfony controller integration. Take the following Symfony controller to calculate some business stats:
use App\Stats\BusinessStatsCalculator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class StatsController extends AbstractController
{
public function __construct(
private BusinessStatsCalculator $statsCalculator,
) {
}
public function index(): Response
{
$stats = $this->statsCalculator->getData();
// ...
}
}
This is a pure Symfony controller. It doesn't use any EasyAdmin code or feature. Now, add the #[AdminRoute]
attribute to your controller action:
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
// ...
class StatsController extends AbstractController
{
// ...
#[AdminRoute('/stats', name: 'stats')]
public function index(): Response
{
// ...
}
}
That's it. The #[AdminRoute]
configuration in controllers is appended to the route path/name of the dashboard(s). If your Symfony application defines an admin dashboard with /admin
route path and admin
route name, this code will add an admin route called admin_stats
with path /admin/stats
.
Browse to that URL and your custom logic is rendered inside the backend with the same layout and navigation as your CRUD pages. Use the new route to link to the controller from your dashboard menu:
public function configureMenuItems(): iterable
{
yield MenuItem::linkToRoute('Statistics', 'fa fa-chart-bar', 'admin_stats');
// ...
}
Click on that menu item and you'll see that the URL is now just /admin/stats
, without the previous awkward query parameters.
Composing Routes
Similar to Symfony's #[Route]
attribute, #[AdminRoute]
supports class-level configuration for common prefixes:
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
// ...
#[AdminRoute('/reports', name: 'reports')]
class ReportController extends AbstractController
{
#[AdminRoute('/sales', name: 'sales')]
public function sales(): Response
{
// ...
}
#[AdminRoute('/inventory', name: 'inventory')]
public function inventory(): Response
{
// ...
}
#[AdminRoute('/customers/{id}', name: 'customer')]
public function customerReport(Customer $customer): Response
{
// ...
}
}
This generates three admin routes:
-
admin_reports_sales
at/admin/reports/sales
-
admin_reports_inventory
at/admin/reports/inventory
-
admin_reports_customer
at/admin/reports/customers/{id}
The routing system concatenates the dashboard prefix, the class-level route, and the action-level route to build the final path and name.
The #[AdminRoute]
attribute can also customize the default CRUD route names and paths, giving you complete control over your admin URL structure.
Multi-Dashboard Control
#[AdminRoute]
creates one admin route per dashboard. If your application has multiple dashboards, you can control where routes are registered with allowedDashboards
(route is created only in these dashboards) and deniedDashboards
(route is created in all dashboards except these):
#[AdminRoute(
'/financial-reports', name: 'financial',
allowedDashboards: [AdminDashboardController::class]
)]
class FinancialReportController extends AbstractController
{
// ...
}
#[AdminRoute(
'/user-reports', name: 'users',
deniedDashboards: [GuestDashboardController::class, '...']
)]
class UserReportController extends AbstractController
{
// ...
}
Recap
The #[AdminRoute]
attribute solves a common need: keeping your business logic in dedicated controllers while providing a unified admin experience. No more awkward URL parameters. Your controllers stay pure Symfony code, but render like native EasyAdmin pages.
✨ If you enjoyed this feature and want to see more like it, consider
sponsoring the EasyAdmin project 🙌💡
Top comments (0)