← Back to list

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…

Alexandre Buleté · 2026-01-27 09:12 · 1 claps · 4.9 min read paywalled
#symfony #sylius #php #cto #domain-driven-design
Open on Medium ↗

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

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
  • /admin protected by ROLE_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