easy-admin-bundle
4
总安装量
4
周安装量
#49607
全站排名
安装命令
npx skills add https://github.com/kgslotwinski/skills --skill easy-admin-bundle
Agent 安装分布
amp
4
github-copilot
4
codex
4
kimi-cli
4
gemini-cli
4
cursor
4
Skill 文档
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 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
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