Symfony: Build a Back Office Fast with the Sylius Stack
First of all: most Symfony apps eventually need an internal back office — even if it’s “just a CRUD”. And that’s exactly where the Sylius…
Symfony: Build a Back Office Fast with the Sylius Stack
First of all: most Symfony apps eventually need an internal back office — even if it’s “just a CRUD”. And that’s exactly where the Sylius Stack shines: it’s a set of Symfony-friendly components that, combined, let you configure an admin UI in minutes instead of spending days rebuilding the same controllers, tables, filters, and templates.
Photo by Ryan Quintal on Unsplash
In this article, we’ll build a Users admin area:
- a grid at /admin/users
- a create form at /admin/users/new
- an edit form at /admin/users/{id}/edit
- a show page at /admin/users/{id}
- a menu entry in the sidebar
- a proper /admin login firewall
All with the Sylius Stack building blocks: Resource, Grid, MenuBuilder, and the admin routes provided by the Admin UI.
Why the Sylius Stack is so productive for Symfony back offices
The “unfair advantage” comes from a simple idea:
- You describe your admin screens with metadata (Resource operations + Grid + Form type)
- The stack provides the generic admin UI (routes, templates, layout, translations)
- You customize only what’s specific to your domain
You get a real grid (sorting/filtering/actions) without hand-coding a controller + repository + template for every list. The Grid Bundle is designed exactly for that “display a grid with sorting and filtering” use case.
Install the minimal admin stack in a Symfony project
Composer + Flex
The Sylius Stack docs propose a minimal install that includes Doctrine, Asset Mapper, the Bootstrap Admin UI, and UI translations:
composer require -W \
doctrine/orm \
doctrine/doctrine-bundle \
pagerfanta/doctrine-orm-adapter \
symfony/asset-mapper \
sylius/bootstrap-admin-ui \
sylius/ui-translations
Symfony Flex will ask you to configure recipes (the docs mention typing “a” or “p”).
What you get right away
The Admin UI provides the essential admin routes (dashboard, login, logout, etc.).
That means you can focus on building your resources (Users, Products, Orders…) instead of scaffolding your own admin foundation.
Build a Users back office using Resource + Grid + Form
1) Create the User entity as a Sylius Resource
Your entity just needs to be a normal Doctrine entity plus:
- implement
ResourceInterface - add
#[AsResource(...)]metadata (section, route prefix, templates directory, operations, etc.)
Here’s a minimal example:
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Grid\UserGrid;
use App\Form\UserType;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Resource\Metadata\AsResource;
use Sylius\Resource\Metadata\Create;
use Sylius\Resource\Metadata\Index;
use Sylius\Resource\Metadata\Show;
use Sylius\Resource\Metadata\Update;
use Sylius\Resource\Model\ResourceInterface;
#[ORM\Entity]
#[ORM\Table(name: 'app_user')]
#[AsResource(
section: 'admin', // influences route names (app_admin_...)
routePrefix: '/admin', // everything under /admin
templatesDir: '@SyliusAdminUi/crud',
formType: UserType::class,
operations: [
new Index(grid: UserGrid::class),
new Create(),
new Update(),
new Show(),
],
)]
class User implements ResourceInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private string $email;
/** @var string[] */
#[ORM\Column(type: 'json')]
private array $roles = [];
#[ORM\Column]
private string $password;
#[ORM\Column(options: ['default' => true])]
private bool $enabled = true;
public function getId(): ?int
{
return $this->id;
}
// getters/setters...
}
What you should notice:
- No controller.
- No routing YAML.
- No “admin templates” to bootstrap.
- Just metadata and your entity.
This is the core “Resource” mindset.
2) Generate the admin grid (the list page)
The cookbook uses Symfony’s Maker command to generate a Grid class:
bin/console make:grid
bin/console cache:clear # refresh grid cache
A grid in Sylius is where you define:
- columns (fields)
- sorting
- filters (optional)
- actions (create/update/delete)
- bulk actions (optional)
A practical UserGrid could look like this (based on the cookbook structure):
<?php
declare(strict_types=1);
namespace App\Grid;
use App\Entity\User;
use Sylius\Bundle\GridBundle\Builder\Action\BulkAction\DeleteAction as BulkDeleteAction;
use Sylius\Bundle\GridBundle\Builder\Action\MainAction\CreateAction;
use Sylius\Bundle\GridBundle\Builder\Action\ItemAction\DeleteAction;
use Sylius\Bundle\GridBundle\Builder\Action\ItemAction\UpdateAction;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\BulkActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\ItemActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\MainActionGroup;
use Sylius\Bundle\GridBundle\Builder\Field\StringField;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;
final class UserGrid extends AbstractGrid implements ResourceAwareGridInterface
{
public static function getName(): string
{
return 'app_user';
}
public function buildGrid(GridBuilderInterface $gridBuilder): void
{
$gridBuilder
->addField(StringField::create('email')->setLabel('Email')->setSortable(true))
->addField(StringField::create('enabled')->setLabel('Enabled')->setSortable(true))
->addActionGroup(MainActionGroup::create(CreateAction::create()))
->addActionGroup(ItemActionGroup::create(
UpdateAction::create(),
DeleteAction::create(),
))
->addActionGroup(BulkActionGroup::create(
BulkDeleteAction::create(),
))
;
}
public function getResourceClass(): string
{
return User::class;
}
}
At this point, you already have a usable “Users list” admin screen.
3) Create the Symfony form type for create/edit
The cookbook uses the Maker command for forms:
bin/console make:form UserType
A minimal form:
<?php
declare(strict_types=1);
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class)
->add('enabled', CheckboxType::class, ['required' => false]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
Because formType: UserType::class is declared in #[AsResource(...)], the create/edit operations automatically use it.
4) Access the grid and forms in the admin
In the cookbook example, once you define an admin resource with routePrefix: '/admin', you get routes like:
..._index→/admin/......_create→/admin/.../new..._update→/admin/.../{id}/edit..._show→/admin/.../{id}
So for our Users:
- Grid:
/admin/users - Create:
/admin/users/new - Edit:
/admin/users/{id}/edit - Show:
/admin/users/{id}
If you want to confirm the exact route names/paths, use:
bin/console debug:router | grep admin_user
Secure /admin with the provided login routes
The Sylius Stack cookbook gives a working security.yaml example for an admin firewall, and it explicitly points out that the login/logout route names are provided by the Sylius Admin UI.
Here’s the relevant snippet:
# config/packages/security.yaml
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_admin_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
admin:
context: admin
pattern: '/admin(?:/.*)?$'
provider: app_admin_user_provider
form_login:
login_path: sylius_admin_ui_login
check_path: sylius_admin_ui_login_check
default_target_path: sylius_admin_ui_dashboard
logout:
path: sylius_admin_ui_logout
target: sylius_admin_ui_login
main:
lazy: true
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin/logout, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: PUBLIC_ACCESS }
Important detail from the docs: place the “main” firewall under the admin firewall, otherwise the admin login won’t work properly.
Now you have:
/admin/login(Admin UI route)/admin/logout/adminprotected byROLE_ADMIN
Add “Users” to the sidebar using MenuBuilder
To customize the sidebar, the docs show decorating the sylius_admin_ui.knp.menu_builder service using #[AsDecorator].
Here’s a clean version that adds a “Users” entry pointing to your grid route (app_admin_user_index):
<?php
declare(strict_types=1);
namespace App\Menu;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Sylius\AdminUi\Knp\Menu\MenuBuilderInterface;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
#[AsDecorator(decorates: 'sylius_admin_ui.knp.menu_builder')]
final readonly class MenuBuilder implements MenuBuilderInterface
{
public function __construct(private FactoryInterface $factory)
{
}
public function createMenu(array $options): ItemInterface
{
$menu = $this->factory->createItem('root');
$menu
->addChild('dashboard', ['route' => 'sylius_admin_ui_dashboard'])
->setLabel('sylius.ui.dashboard')
->setLabelAttribute('icon', 'tabler:dashboard')
;
$menu
->addChild('users', ['route' => 'app_admin_user_index'])
->setLabel('Users')
->setLabelAttribute('icon', 'tabler:users')
;
return $menu;
}
}
This is the kind of “small but powerful” customization you want in a Symfony app: one tiny class, and your admin navigation is done.
Optional: customize the User “show” page with Twig Hooks
If you want to inject custom content into the generic CRUD pages, the cookbook shows using sylius_twig_hooks to define a hook and point to your own template.
Example (adapted to user):
# config/packages/sylius_bootstrap_admin_ui.yaml
sylius_twig_hooks:
hooks:
'sylius_admin.user.show.content':
body:
template: 'user/show/content/body.html.twig'
And in your Twig template:
{# templates/user/show/content/body.html.twig #}
{% set user = hookable_metadata.context.user %}
<div class="page-body">
<div class="container-xl">
<div class="row">
<div class="col-12">
<h3>{{ user.email }}</h3>
<p>Enabled: {{ user.enabled ? 'yes' : 'no' }}</p>
</div>
</div>
</div>
</div>
The reusable recipe for “admin screens in Symfony”
Once you’ve done it once, your mental model becomes ridiculously repeatable:
- Resource Entity + ResourceInterface + #[AsResource(…)] operations
- Grid One Grid class describing list columns/actions
- Form One Symfony Form type for create/edit
- Menu One menu entry to link it
- Security One admin firewall using the Admin UI login routes
That’s it. That’s the system.
If you are also interested in implementing Sylius Stack in a DDD environment, you can find out more about this in my other articles, such as:
메타데이터
- post_id
- 925e098b8f4e
- slug
- symfony-build-a-back-office-fast-with-the-sylius-stack-925e098b8f4e
- url
- https://medium.com/@bulete.alexandre/symfony-build-a-back-office-fast-with-the-sylius-stack-925e098b8f4e
- canonical_url
- https://medium.com/@bulete.alexandre/symfony-build-a-back-office-fast-with-the-sylius-stack-925e098b8f4e
- author_url
- https://medium.com/@bulete.alexandre
- status
- ok
- fetched_at
- 2026-07-14 15:30:42