What You're Building and Why It Matters
A custom backoffice in Sulu CMS is a tailored admin interface where your team manages content, users, and site settings without seeing the default dashboard. Instead of the standard Sulu layout, you build exactly what your workflow needs — specific fields, custom buttons, filtered data views, and role-based screens. This saves time because editors see only what they need to edit, and it reduces mistakes because the interface guides them toward correct data entry.
Sulu's architecture makes this possible because the admin panel is built on the same bundle system as the rest of the platform. You write a custom bundle, define routes and controllers, add Twig templates, and wire everything into Sulu's permission system. The result feels native to Sulu — it uses the same styling, authentication, and user management — but it's entirely yours.
Key Takeaways
- A custom backoffice is a separate admin interface you build as a Sulu bundle that replaces or supplements the default dashboard for specific user roles.
- You need a working Sulu installation, basic knowledge of Symfony (Sulu's underlying framework), and familiarity with Twig templating and PHP controllers.
- The core steps are creating a bundle, defining routes, building controllers that fetch and display data, and registering the interface in Sulu's permission system.
- Custom backoffices inherit Sulu's authentication and styling, so you don't rebuild security or design from scratch.
Setting Up Your Custom Bundle
Start by creating a new Symfony bundle inside your Sulu project. Open a terminal in your project root and run the bundle generator. This creates the folder structure and base files you need. Name it something descriptive like CustomBackofficeBundle so you and your team know what it does.
After generation, register the bundle in your config/bundles.php file. Add a line that imports your new bundle class. Then create a Resources/config folder inside your bundle where you'll store routing and service configuration. Sulu reads these files automatically when the bundle loads, so any routes you define there become available immediately.
Test that the bundle loads by running php bin/console debug:bundle and confirming your bundle appears in the list. If it doesn't, check that the bundle class is properly namespaced and that bundles.php imports it correctly.
Defining Routes and Creating Controllers
Routes tell Sulu which URLs map to which pages in your backoffice. Create a routes.yaml file in your bundle's Resources/config folder. Define a route for your main dashboard page — for example, /admin/custom-backoffice — and point it to a controller class and method.
In your bundle's Controller folder, create a CustomBackofficeController.php file. This controller is a PHP class that extends Sulu's base controller. Write a method called indexAction() that fetches the data your backoffice needs — user counts, recent content, pending tasks, whatever your team tracks. Use Symfony's dependency injection to access repositories and services. Return a response that renders a Twig template.
For example, if your backoffice shows pending articles, your controller queries the article repository, filters by status, and passes the results to the template. The template then loops through the articles and displays them in a table or card layout. Keep the controller focused on data retrieval; let the template handle display.
Building Templates That Match Sulu's Design
Create a Resources/views folder in your bundle and add a Twig template file — for example, custom_backoffice/index.html.twig. This template should extend Sulu's admin base template so it inherits the header, navigation, and styling. Start with {% extends "@SuluAdmin/Admin/admin.html.twig" %} to use Sulu's standard layout.
Inside the template, define blocks for the page title and content. Use Sulu's CSS classes and component patterns so your custom backoffice looks like it belongs in the admin panel. Sulu provides button styles, form components, and table layouts that you can reuse. Check Sulu's documentation or inspect existing admin pages to see which classes and components are available.
If you need interactive features — sorting, filtering, or inline editing — use JavaScript that Sulu already loads. Don't add heavy dependencies unless necessary. Keep the template readable so future developers can modify it without confusion.
Connecting Data Sources and Repositories
Your backoffice needs to pull data from somewhere. Most often, that's the Sulu database through Doctrine repositories. In your controller, inject the repository for the entity you want to display — for example, the article repository or user repository. Call methods on that repository to fetch filtered or sorted data.
If your data comes from an external API or a custom table, create a service class that handles the connection. Inject that service into your controller instead of the repository. This keeps your controller clean and makes the service reusable if other parts of your backoffice need the same data.
Always filter data based on the logged-in user's permissions. Sulu provides a security context system; check whether the current user has permission to view or edit the data before returning it to the template. This prevents editors from seeing content they shouldn't access.
Adding Permissions and Access Control
Sulu's permission system controls who sees your custom backoffice. Define a security context for your backoffice in your bundle's configuration — for example, sulu.custom_backoffice.view. Then, in your controller, check that the logged-in user has that permission before rendering the page.
Use Sulu's @Security annotation or the denyAccessUnlessGranted() method in your controller. If a user without permission tries to access the backoffice, Sulu shows an access denied page automatically. You can also create different routes for different roles — for example, a manager backoffice and an editor backoffice — and assign different permissions to each.
In Sulu's admin panel, navigate to Settings > Roles and create or edit roles to grant the new security context. Assign the context to the roles that should see your custom backoffice. Users in those roles will now see the backoffice when they log in.
Testing and Deploying Your Backoffice
Before going live, test your backoffice with different user roles to make sure permissions work correctly. Log in as an editor, a manager, and an admin to confirm each sees the right data and buttons. Check that the interface loads quickly even with large datasets — if it's slow, optimize your database queries or add pagination.
Clear Sulu's cache after making changes to routes or configuration. Run php bin/console cache:clear to ensure Sulu loads your latest code. In production, use php bin/console cache:clear --env=prod to clear the production cache.
Deploy your bundle the same way you deploy the rest of your Sulu project. Commit the bundle code to version control, push to your deployment branch, and run the deployment script. Sulu will load the bundle automatically on the next request.
Frequently Asked Questions
Can I add forms to my custom backoffice?
Yes. Use Symfony's form builder in your controller to create forms that match Sulu's styling. Render the form in your Twig template using Sulu's form theme. When the form submits, handle the data in another controller method, validate it, and save it to the database.
How do I add custom JavaScript or CSS to my backoffice?
Include stylesheets and scripts in your Twig template using standard HTML tags. Sulu loads them alongside the admin panel's existing assets. Keep custom assets lightweight and avoid conflicts with Sulu's existing JavaScript libraries.
What if I need to display data from multiple entities?
Inject multiple repositories into your controller and fetch data from each one. Pass all the data to your template as separate variables. The template can then display each dataset in its own section or table.
Can I use the custom backoffice for specific content types only?
Yes. In your controller, query only the content type you want to display. Use Sulu's content repository and filter by the content type name. This lets you build a focused backoffice for articles, products, or any other type you define.
How do I handle errors in the custom backoffice?
Use Symfony's exception handling. If a database query fails or a permission check fails, Sulu catches the exception and shows an error page. For user-facing errors — like "no results found" — return a message in your template instead of throwing an exception.