name: easy-admin-bundle version: 1.1.0 compatibility: easyadmin: "^4.0" php: ">=8.1" symfony: ">=5.4" updated: 2026-02-23 description: "Rapid Symfony admin panel development with EasyAdminBundle. Capabilities: CRUD generation, dashboard creation, entity management, form customization, menu configuration, field configuration, action customization, filters, permissions, batch actions, custom themes, file uploads, image handling, associations, submenus, custom queries, event listeners. Use for: admin panels, backend interfaces, content management, data administration, CRUD operations, user management, product catalogs, blog administration, e-commerce backends. Triggers: easyadmin, symfony admin, crud, admin panel, dashboard, backend, entity crud, admin interface, symfony backend, easyadmin, easyadmin bundle, admin generator, backend panel" allowed-tools: Bash(composer *, symfony *, php bin/console *), Read, Write, Edit, Glob, Grep
EasyAdmin Bundle Skill
Build powerful Symfony admin panels quickly with EasyAdminBundle. This skill provides complete examples and patterns for creating production-ready admin interfaces.
Version: This skill covers EasyAdminBundle 4.x (current stable) Requirements: PHP 8.1+, Symfony 5.4/6.x/7.x/8.x, Doctrine ORM Official Docs: https://github.com/EasyCorp/EasyAdminBundle/tree/4.x/doc Repository: https://github.com/EasyCorp/EasyAdminBundle
📚 Quick Reference: See API Reference section for complete field types, actions, and CRUD configuration methods
Quick Start
# Install EasyAdmin bundle
composer require easycorp/easyadmin-bundle
# Create a dashboard controller
php bin/console make:admin:dashboard
# Create CRUD controllers for your entities
php bin/console make:admin:crud
Complete Dashboard Setup
Basic Dashboard Controller
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;
use App\Entity\User;
use App\Entity\Post;
use App\Entity\Category;
use App\Entity\Comment;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('My Admin Panel')
->setFaviconPath('favicon.ico')
->setTranslationDomain('admin')
->setTextDirection('ltr')
->renderContentMaximized()
->renderSidebarMinimized()
->disableDarkMode();
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::section('Content');
yield MenuItem::linkToCrud('Posts', 'fa fa-newspaper', Post::class);
yield MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class);
yield MenuItem::linkToCrud('Comments', 'fa fa-comments', Comment::class);
yield MenuItem::section('Users');
yield MenuItem::linkToCrud('Users', 'fa fa-user', User::class);
yield MenuItem::section('External');
yield MenuItem::linkToUrl('Homepage', 'fa fa-globe', '/');
yield MenuItem::linkToLogout('Logout', 'fa fa-sign-out');
}
}
Dashboard with Submenus
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
// Submenu example
yield MenuItem::subMenu('Content', 'fa fa-file')->setSubItems([
MenuItem::linkToCrud('Posts', 'fa fa-newspaper', Post::class),
MenuItem::linkToCrud('Pages', 'fa fa-file-text', Page::class),
MenuItem::linkToCrud('Media', 'fa fa-images', Media::class),
]);
yield MenuItem::subMenu('E-commerce', 'fa fa-shopping-cart')->setSubItems([
MenuItem::linkToCrud('Products', 'fa fa-box', Product::class),
MenuItem::linkToCrud('Orders', 'fa fa-shopping-bag', Order::class),
MenuItem::linkToCrud('Customers', 'fa fa-users', Customer::class),
]);
}
Field Types Reference
Basic Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TelephoneField;
public function configureFields(string $pageName): iterable
{
yield TextField::new('title')->setMaxLength(255);
yield TextEditorField::new('content'); // Rich text editor
yield TextareaField::new('description')->setMaxLength(500);
yield NumberField::new('price')->setNumDecimals(2);
yield IntegerField::new('quantity');
yield EmailField::new('email');
yield UrlField::new('website');
yield TelephoneField::new('phone');
}
Date and Time Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TimeField;
public function configureFields(string $pageName): iterable
{
yield DateField::new('publishedAt')
->setFormat('dd/MM/yyyy');
yield DateTimeField::new('createdAt')
->setFormat('dd/MM/yyyy HH:mm')
->hideOnForm();
yield TimeField::new('openingTime')
->setFormat('HH:mm');
}
Boolean and Choice Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
public function configureFields(string $pageName): iterable
{
yield BooleanField::new('isPublished')
->renderAsSwitch();
yield ChoiceField::new('status')
->setChoices([
'Draft' => 'draft',
'Published' => 'published',
'Archived' => 'archived',
])
->renderAsBadges([
'draft' => 'warning',
'published' => 'success',
'archived' => 'secondary',
]);
yield ChoiceField::new('roles')
->setChoices([
'User' => 'ROLE_USER',
'Editor' => 'ROLE_EDITOR',
'Admin' => 'ROLE_ADMIN',
])
->allowMultipleChoices()
->renderExpanded(); // Show as checkboxes
}
Association Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
public function configureFields(string $pageName): iterable
{
// Many-to-One
yield AssociationField::new('category')
->setRequired(true);
// One-to-Many / Many-to-Many
yield AssociationField::new('tags')
->setFormTypeOptions([
'by_reference' => false,
])
->autocomplete(); // Ajax autocomplete for large datasets
// Custom query for association
yield AssociationField::new('author')
->setQueryBuilder(
fn (QueryBuilder $qb) => $qb
->andWhere('entity.isActive = :active')
->setParameter('active', true)
);
}
File and Image Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\FileField;
public function configureFields(string $pageName): iterable
{
yield ImageField::new('thumbnail')
->setBasePath('uploads/images')
->setUploadDir('public/uploads/images')
->setUploadedFileNamePattern('[randomhash].[extension]')
->setRequired(false);
yield ImageField::new('gallery')
->setBasePath('uploads/gallery')
->setUploadDir('public/uploads/gallery')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
->setFormTypeOptions(['multiple' => true]);
yield FileField::new('attachment')
->setBasePath('uploads/files')
->setUploadDir('public/uploads/files');
}
Money and Percent Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\PercentField;
public function configureFields(string $pageName): iterable
{
yield MoneyField::new('price')
->setCurrency('USD')
->setStoredAsCents(false);
yield MoneyField::new('priceInCents')
->setCurrency('EUR')
->setStoredAsCents(true);
yield PercentField::new('discount')
->setNumDecimals(2);
}
Other Useful Fields
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ColorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CountryField;
use EasyCorp\Bundle\EasyAdminBundle\Field\LanguageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\LocaleField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TimezoneField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CodeEditorField;
public function configureFields(string $pageName): iterable
{
yield SlugField::new('slug')
->setTargetFieldName('title');
yield ColorField::new('brandColor');
yield CountryField::new('country');
yield LanguageField::new('language');
yield LocaleField::new('locale');
yield TimezoneField::new('timezone');
yield CodeEditorField::new('customCss')
->setLanguage('css')
->setNumOfRows(10);
}
Complete CRUD Controller Examples
User CRUD with All Features
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Filter\BooleanFilter;
use EasyCorp\Bundle\EasyAdminBundle\Filter\DateTimeFilter;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('User')
->setEntityLabelInPlural('Users')
->setPageTitle('index', 'User Management')
->setPageTitle('new', 'Create User')
->setPageTitle('edit', 'Edit %entity_label_singular%')
->setSearchFields(['username', 'email', 'firstName', 'lastName'])
->setDefaultSort(['createdAt' => 'DESC'])
->setPaginatorPageSize(30)
->setEntityPermission('ROLE_ADMIN');
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->hideOnForm();
yield TextField::new('username')
->setColumns(6);
yield EmailField::new('email')
->setColumns(6);
yield TextField::new('firstName')
->setColumns(6)
->hideOnIndex();
yield TextField::new('lastName')
->setColumns(6)
->hideOnIndex();
yield ImageField::new('avatar')
->setBasePath('uploads/avatars')
->setUploadDir('public/uploads/avatars')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
->hideOnIndex();
yield ArrayField::new('roles')
->hideOnIndex();
yield BooleanField::new('isVerified')
->renderAsSwitch(false);
yield BooleanField::new('isActive')
->renderAsSwitch(false);
yield DateTimeField::new('createdAt')
->hideOnForm();
yield DateTimeField::new('lastLoginAt')
->hideOnForm()
->hideOnIndex();
}
public function configureFilters(Filters $filters): Filters
{
return $filters
->add('username')
->add('email')
->add(BooleanFilter::new('isActive'))
->add(BooleanFilter::new('isVerified'))
->add(DateTimeFilter::new('createdAt'))
->add(DateTimeFilter::new('lastLoginAt'));
}
public function configureActions(Actions $actions): Actions
{
$impersonate = Action::new('impersonate', 'Impersonate')
->linkToCrudAction('impersonate')
->setCssClass('btn btn-warning')
->displayIf(fn (User $user) => $this->isGranted('ROLE_SUPER_ADMIN'));
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $impersonate)
->update(Crud::PAGE_INDEX, Action::DELETE, function (Action $action) {
return $action->displayIf(fn (User $user) => $user->getId() !== $this->getUser()->getId());
});
}
}
Product CRUD with Categories and Images
namespace App\Controller\Admin;
use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class ProductCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Product::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products')
->setSearchFields(['name', 'sku', 'description'])
->setDefaultSort(['createdAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('name')
->setColumns(8);
yield SlugField::new('slug')
->setTargetFieldName('name')
->setColumns(4)
->hideOnIndex();
yield TextField::new('sku')
->setColumns(6);
yield MoneyField::new('price')
->setCurrency('USD')
->setColumns(6);
yield IntegerField::new('stock')
->setColumns(6);
yield AssociationField::new('category')
->setColumns(6)
->autocomplete();
yield AssociationField::new('tags')
->hideOnIndex()
->autocomplete();
yield TextEditorField::new('description')
->hideOnIndex();
yield ImageField::new('image')
->setBasePath('uploads/products')
->setUploadDir('public/uploads/products')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]');
yield BooleanField::new('isFeatured')
->renderAsSwitch(false);
yield BooleanField::new('isActive')
->renderAsSwitch(false);
yield DateTimeField::new('createdAt')
->hideOnForm();
}
}
Blog Post CRUD with Rich Content
namespace App\Controller\Admin;
use App\Entity\Post;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class PostCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Post::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Post')
->setEntityLabelInPlural('Blog Posts')
->setSearchFields(['title', 'content', 'excerpt'])
->setDefaultSort(['publishedAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('title');
yield SlugField::new('slug')
->setTargetFieldName('title')
->hideOnIndex();
yield TextField::new('excerpt')
->setMaxLength(200)
->hideOnIndex();
yield TextEditorField::new('content')
->hideOnIndex();
yield ImageField::new('featuredImage')
->setBasePath('uploads/posts')
->setUploadDir('public/uploads/posts');
yield AssociationField::new('author')
->autocomplete();
yield AssociationField::new('category');
yield AssociationField::new('tags')
->hideOnIndex()
->autocomplete();
yield ChoiceField::new('status')
->setChoices([
'Draft' => 'draft',
'Published' => 'published',
'Archived' => 'archived',
])
->renderAsBadges([
'draft' => 'warning',
'published' => 'success',
'archived' => 'secondary',
]);
yield DateTimeField::new('publishedAt')
->hideOnIndex();
yield DateTimeField::new('createdAt')
->hideOnForm();
yield DateTimeField::new('updatedAt')
->hideOnForm()
->hideOnIndex();
}
}
Custom Actions
Add Custom Action to CRUD
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use Symfony\Component\HttpFoundation\Response;
class ProductCrudController extends AbstractCrudController
{
public function configureActions(Actions $actions): Actions
{
// Create custom action
$exportAction = Action::new('export', 'Export CSV')
->linkToCrudAction('exportCsv')
->setCssClass('btn btn-success')
->createAsGlobalAction(); // Shows in index page header
$duplicateAction = Action::new('duplicate', 'Duplicate')
->linkToCrudAction('duplicateProduct')
->setCssClass('btn btn-info');
return $actions
->add(Crud::PAGE_INDEX, $exportAction)
->add(Crud::PAGE_DETAIL, $duplicateAction)
->add(Crud::PAGE_INDEX, Action::DETAIL)
->reorder(Crud::PAGE_INDEX, [Action::DETAIL, Action::EDIT, 'duplicate', Action::DELETE]);
}
public function exportCsv(): Response
{
$products = $this->entityManager->getRepository(Product::class)->findAll();
// Generate CSV content
$csv = "ID,Name,SKU,Price\n";
foreach ($products as $product) {
$csv .= sprintf("%d,%s,%s,%.2f\n",
$product->getId(),
$product->getName(),
$product->getSku(),
$product->getPrice()
);
}
return new Response($csv, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="products.csv"',
]);
}
public function duplicateProduct(AdminContext $context): Response
{
$product = $context->getEntity()->getInstance();
$newProduct = clone $product;
$newProduct->setName($product->getName() . ' (Copy)');
$newProduct->setSku($product->getSku() . '-copy');
$this->entityManager->persist($newProduct);
$this->entityManager->flush();
$this->addFlash('success', 'Product duplicated successfully');
return $this->redirect($context->getReferrer());
}
}
Batch Actions
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
public function configureActions(Actions $actions): Actions
{
$batchPublish = Action::new('batchPublish', 'Publish selected')
->linkToCrudAction('batchPublish')
->addCssClass('btn btn-success')
->setIcon('fa fa-check');
return $actions
->addBatchAction($batchPublish);
}
public function batchPublish(BatchActionDto $batchActionDto): Response
{
$entityManager = $this->entityManager;
foreach ($batchActionDto->getEntityIds() as $id) {
$post = $entityManager->find(Post::class, $id);
if ($post) {
$post->setStatus('published');
$post->setPublishedAt(new \DateTime());
}
}
$entityManager->flush();
$this->addFlash('success', sprintf('Published %d posts', count($batchActionDto->getEntityIds())));
return $this->redirect($batchActionDto->getReferrerUrl());
}
Advanced Configurations
Conditional Field Display
public function configureFields(string $pageName): iterable
{
yield TextField::new('title');
// Show only on index page
yield TextField::new('summary')->onlyOnIndex();
// Show only on forms (new/edit)
yield TextEditorField::new('content')->onlyOnForms();
// Show only on detail page
yield TextField::new('fullDetails')->onlyOnDetail();
// Hide on specific pages
yield DateTimeField::new('createdAt')
->hideOnForm()
->hideOnIndex();
// Conditional display based on context
if (Crud::PAGE_EDIT === $pageName) {
yield DateTimeField::new('updatedAt')->setFormTypeOptions(['disabled' => true]);
}
}
Custom Form Layouts
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
public function configureFields(string $pageName): iterable
{
yield FormField::addPanel('Basic Information');
yield TextField::new('name');
yield TextField::new('sku');
yield FormField::addPanel('Pricing')->setIcon('fa fa-dollar-sign');
yield MoneyField::new('price')->setCurrency('USD');
yield MoneyField::new('costPrice')->setCurrency('USD');
yield PercentField::new('taxRate');
yield FormField::addPanel('Inventory');
yield IntegerField::new('stock');
yield IntegerField::new('minStock');
yield BooleanField::new('trackInventory');
yield FormField::addPanel('SEO')->setHelp('Search engine optimization settings');
yield TextField::new('metaTitle');
yield TextareaField::new('metaDescription');
}
Security and Permissions
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityPermission('ROLE_ADMIN') // Required role for all operations
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products');
}
public function configureActions(Actions $actions): Actions
{
return $actions
// Only super admins can delete
->setPermission(Action::DELETE, 'ROLE_SUPER_ADMIN')
// Conditional permissions
->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) {
return $action->displayIf(function (Product $product) {
return $this->isGranted('ROLE_EDITOR') || $product->getAuthor() === $this->getUser();
});
});
}
Custom Queries and Filters
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
// Only show active products
$qb->andWhere('entity.isActive = :active')
->setParameter('active', true);
// Only show products from current user's store
if (!$this->isGranted('ROLE_ADMIN')) {
$qb->andWhere('entity.store = :store')
->setParameter('store', $this->getUser()->getStore());
}
return $qb;
}
Event Listeners and Hooks
Modify Entity Before Persist
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProductSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
BeforeEntityPersistedEvent::class => ['setCreatedAt'],
BeforeEntityUpdatedEvent::class => ['setUpdatedAt'],
];
}
public function setCreatedAt(BeforeEntityPersistedEvent $event): void
{
$entity = $event->getEntityInstance();
if ($entity instanceof Product) {
$entity->setCreatedAt(new \DateTime());
}
}
public function setUpdatedAt(BeforeEntityUpdatedEvent $event): void
{
$entity = $event->getEntityInstance();
if ($entity instanceof Product) {
$entity->setUpdatedAt(new \DateTime());
}
}
}
Configuration Files
Route Configuration
# config/routes.yaml
admin:
resource: App\Controller\Admin\DashboardController
type: easyadmin
prefix: /admin
Security Configuration
# config/packages/security.yaml
security:
# ... other config
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
role_hierarchy:
ROLE_EDITOR: ROLE_USER
ROLE_ADMIN: ROLE_EDITOR
ROLE_SUPER_ADMIN: ROLE_ADMIN
EasyAdmin Configuration
# config/packages/easyadmin.yaml
easy_admin:
site_name: 'My Admin Panel'
formats:
date: 'd/m/Y'
time: 'H:i'
datetime: 'd/m/Y H:i:s'
design:
brand_color: '#1976D2'
menu:
- { label: 'Dashboard', icon: 'fa fa-home', route: 'admin' }
# Custom assets
assets:
css:
- 'css/admin.css'
js:
- 'js/admin.js'
Troubleshooting
Routes Not Found
# Clear cache
php bin/console cache:clear
# Verify routes
php bin/console debug:router | grep admin
Images Not Displaying
Ensure your public directory structure matches:
public/
uploads/
images/
products/
avatars/
Association Fields Empty
Add by_reference => false for collections:
yield AssociationField::new('tags')
->setFormTypeOptions(['by_reference' => false]);
Autocomplete Not Working
Install and configure autocomplete:
composer require symfony/ux-autocomplete
php bin/console importmap:install
Performance Tips
- Use pagination - Keep default page size reasonable (20-50 items)
- Limit search fields - Only include fields that need to be searchable
- Use autocomplete for associations - Especially with large datasets
- Disable unnecessary features - Hide detail pages if not needed
- Cache queries - Use query result caching for complex filters
Common Patterns
Multi-Tenant CRUD
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
$qb->andWhere('entity.tenant = :tenant')
->setParameter('tenant', $this->getUser()->getTenant());
return $qb;
}
public function persistEntity(EntityManagerInterface $em, $entityInstance): void
{
$entityInstance->setTenant($this->getUser()->getTenant());
parent::persistEntity($em, $entityInstance);
}
Soft Delete Integration
use Doctrine\ORM\QueryBuilder;
public function createIndexQueryBuilder(...): QueryBuilder
{
$qb = parent::createIndexQueryBuilder(...);
$qb->andWhere('entity.deletedAt IS NULL');
return $qb;
}
Audit Trail
public function updateEntity(EntityManagerInterface $em, $entityInstance): void
{
$entityInstance->setUpdatedBy($this->getUser());
$entityInstance->setUpdatedAt(new \DateTime());
parent::updateEntity($em, $entityInstance);
}
Requirements
- PHP: 8.1 or higher
- Symfony: 5.4, 6.x, 7.x, or 8.x
- Doctrine ORM: Required for entity management
- Twig: Template engine
Installation
# Install bundle (version 4.x)
composer require easycorp/easyadmin-bundle
# Optional: Install UX components for enhanced features
composer require symfony/ux-autocomplete
# Generate dashboard
php bin/console make:admin:dashboard
# Generate CRUD controllers
php bin/console make:admin:crud
API Reference
Complete API reference for quick lookup. See files in references/ directory:
📋 Fields
Complete list of all 31 field types with their configuration methods:
- Common Methods: hideOnIndex(), onlyOnForms(), setColumns(), setRequired(), etc.
- Field Types: TextField, AssociationField, ImageField, MoneyField, DateTimeField, BooleanField, ChoiceField, and 24 more
- Form Layout: FormField panels, tabs, columns, rows
- Doctrine Mappings: Type to field mappings
⚡ Actions
Actions API and configuration methods:
- Built-in Actions: INDEX, DETAIL, EDIT, NEW, DELETE, SAVE_AND_RETURN, etc.
- Configuration: add(), remove(), update(), disable(), setPermission(), reorder()
- Custom Actions: linkToCrudAction(), linkToRoute(), linkToUrl()
- Batch Actions: addBatchAction(), BatchActionDto usage
🔧 CRUD
CRUD controller configuration and lifecycle methods:
- configureCrud(): setEntityLabelInSingular(), setSearchFields(), setDefaultSort(), setPaginatorPageSize(), etc.
- Query Builders: createIndexQueryBuilder(), createEditQueryBuilder(), createNewQueryBuilder()
- Lifecycle: createEntity(), persistEntity(), updateEntity(), deleteEntity()
- Routes & Context: AdminContext, generateUrl(), getReferrer()
Official Documentation
Main Documentation
- Repository: https://github.com/EasyCorp/EasyAdminBundle
- Documentation (4.x): https://github.com/EasyCorp/EasyAdminBundle/tree/4.x/doc
- Symfony Docs: https://symfony.com/bundles/EasyAdminBundle/current/index.html
Key Documentation Files
- CRUD Operations - Complete CRUD configuration
- Dashboard - Dashboard setup and configuration
- Fields Reference - All available field types
- Actions - Custom actions and batch operations
- Filters - Filtering configuration
- Security - Permission and security setup
- Events - Event listeners and hooks
- Design - Customizing the UI
- Upgrade Guide - Migration between versions
Getting Latest Information
# Clone the repository to browse documentation locally
git clone https://github.com/EasyCorp/EasyAdminBundle.git
cd EasyAdminBundle
# Switch to 4.x branch
git checkout 4.x
# View documentation
cd doc
ls -la # List all documentation files
# Read specific documentation
cat fields.rst
cat crud.rst
cat dashboards.rst
Checking for Updates
# Check installed version
composer show easycorp/easyadmin-bundle
# Update to latest 4.x version
composer update easycorp/easyadmin-bundle
# Check for newer versions
composer outdated easycorp/easyadmin-bundle
Version Notes
- 4.x (Current Stable): Production ready, actively maintained
- 5.x (Beta): In development, use for testing only
- This skill is based on 4.x patterns and syntax
Community Resources
- GitHub Issues: https://github.com/EasyCorp/EasyAdminBundle/issues
- GitHub Discussions: https://github.com/EasyCorp/EasyAdminBundle/discussions
- Symfony Community: https://symfony.com/community
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!