open-dxp/admin-bundle
OpenDXP Admin Bundle provides the backend administration interface for OpenDXP applications, bundling management screens, navigation, and supporting services into a reusable PHP package. It helps teams integrate consistent admin workflows and operate application data more efficiently.
1. Install and register the bundle
composer require open-dxp/admin-bundle
Register it in config/bundles.php. Order matters — place it after the OpenDXP core/framework bundles so its routes and security extensions load last:
return [
// ... OpenDXP core bundles ...
OpenDxp\Bundle\AdminBundle\OpenDxpAdminBundle::class => ['all' => true],
];
2. Make sure the admin firewall exists
The bundle ships routes/controllers, but authentication is wired by your app. In config/packages/security.yaml, the admin firewall must be declared before your main site firewall, because Symfony matches firewalls in order:
security:
firewalls:
admin:
pattern: ^/admin
provider: open_dxp_users
form_login:
login_path: /admin/login
check_path: /admin/login
logout:
path: /admin/logout
main:
pattern: ^/
# ...
3. Import routes
Check whether a Flex recipe dropped config/routes/open_dxp_admin.yaml for you. If not, import the bundle's route file explicitly (inspect config/ inside the package for the exact resource path — it differs between the XML/YAML/PHP loader styles used across OpenDXP versions):
# config/routes/open_dxp_admin.yaml
open_dxp_admin:
resource: '@OpenDxpAdminBundle/config/routes.yaml'
prefix: /admin
4. First run
bin/console cache:clear
bin/console assets:install public --symlink
bin/console debug:router | grep admin
Then open /admin. If you get a 403/redirect loop, it is almost always the firewall pattern or a missing user provider — not the bundle.
Where to look first, in this order
src/Controller/ — see what the base admin controller provides (layout variables, language/session handling, CSRF expectations).config/ — routes, services.yaml, security extensions, and any tagged-service/event declarations. This is the real API surface.templates/ — the layout(s) your own admin screens should extend.src/Event/ and src/EventListener/ — the menu/navigation/settings extension points (names follow the upstream Pimcore lineage; confirm by grepping for dispatch().CHANGELOG.md / git tags — with a single recent release and no published docs, this is your contract.First use case to aim for: a custom admin screen inside an existing bundle that extends the admin layout, appears in the navigation, and is protected by the same ^/admin firewall. That exercises routing, templating, assets, auth, and translations in one pass.
Pattern 1 — Adding a custom admin module
Keep it in your own bundle rather than patching the admin bundle:
// src/Controller/Admin/ReportController.php
#[Route('/admin/reports', name: 'app_admin_reports')]
class ReportController extends AbstractController
{
public function index(): Response
{
return $this->render('@App/admin/reports/index.html.twig', [
'stats' => $this->reportService->summarise(),
]);
}
}
Extend the admin layout rather than base.html.twig so you inherit the chrome, asset includes, and locale switching:
{# templates/admin/reports/index.html.twig #}
{% extends '@OpenDxpAdminBundle/layout.html.twig' %}
{% block content %}
<h1>{{ 'reports.title'|trans({}, 'admin') }}</h1>
{{ include('@App/admin/reports/_table.html.twig') }}
{% endblock %}
Verify the actual layout path with bin/console debug:twig | grep -i admin — bundle template layout names are the single most common source of "template not found" during adoption.
Pattern 2 — Hooking into the admin navigation
Prefer events over overriding controllers. Subscribe to the navigation/settings event the bundle dispatches during the index action and append your entry:
class AdminNavigationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [AdminIndexEvent::class => 'onAdminIndex'];
}
public function onAdminIndex(AdminIndexEvent $event): void
{
$event->addMenuItem([
'label' => 'admin.nav.reports',
'path' => '/admin/reports',
]);
}
}
Run bin/console debug:event-dispatcher to confirm the concrete event class and whether the handler receives a mutable collection or a dispatcher-scoped config array. If no event exists in your version, the fallback is a service tagged with the bundle's menu tag (declared in config/services.yaml) or a template override — in that order of preference.
Pattern 3 — Translations
Admin strings should live in the admin translation domain so they follow the user's backend locale, and stay separate from frontend strings:
# translations/admin.en.yaml
reports:
title: 'Reports'
Pattern 4 — Shared identity with the frontend
The most common integration is a single User entity/provider serving both ^/admin and the public site, with roles gating the admin firewall. Keep the role check in an access control rule, not in controllers:
security:
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
Pattern 5 — Local development against the package
When you need to change or debug the bundle itself:
# in your app's composer.json, use a path repository
"repositories": [{ "type": "path", "url": "../admin-bundle" }]
composer require open-dxp/admin-bundle:"@dev"
bin/console assets:install public --symlink
Pattern 6 — Functional tests for admin screens
Symfony's loginUser() is far less brittle than driving the login form:
$client->loginUser($adminUser);
$client->request('GET', '/admin/reports');
self::assertResponseIsSuccessful();
Beware: admin pages often make XHR calls for grids/charts. Assert on the server-rendered shell, not on fully hydrated DOM.
License is NOASSERTION. This is the single biggest adoption risk. Read the actual LICENSE file in the repo and confirm the terms map to the OpenDXP project license before shipping commercially. Do not assume MIT.
Almost no downstream users (0 dependents, 3 stars). You will not find Stack Overflow answers. Budget for reading source, and pin an exact tag (composer require open-dxp/admin-bundle:^X.Y with a lockfile) rather than floating versions — the release date implies active development and there is no published BC policy.
Firewall order is the #1 operational failure mode. An admin firewall declared after main will silently never match, producing login redirect loops or 403s that look like bundle bugs. Confirm with bin/console debug:router admin_login and debug:firewall.
Route prefix collisions. If you import the bundle without a prefix, or a catch-all site route (/{slug}) is imported first, admin URLs get swallowed. Give admin routes the ^/admin prefix and import them early; check resolution with bin/console debug:router --show-controllers | grep admin.
Don't edit vendor/. For UI tweaks, use Symfony's bundle override convention:
templates/bundles/OpenDxpAdminBundle/<path-relative-to-bundle-templates>/...
Confirm the exact bundle namespace casing from getContainer()->getParameter('kernel.bundles'); a one-character namespace mismatch means your override is silently ignored.
Asset pipeline confusion. After composer update, admin CSS/JS 404s are almost always stale published assets. bin/console assets:install public --symlink then hard-refresh (browser cache holds old hashed bundles). In production, run it without --symlink and clear the HTTP cache in front of the app.
Config and translation caching. Changes to security.yaml or any bundle config require cache:clear in dev, and translation changes require cache:clear too. In prod, deploy must include cache:clear — a warm cache from the previous release will serve old admin service definitions.
CSRF on admin AJAX. Admin grids and inline-edit endpoints typically expect the session CSRF token. Extract it from a meta tag rendered in the layout and send it as a header; hardcoding a token or omitting it yields 419-style failures that look like auth problems.
Don't load admin assets on frontend routes. If the layout is accidentally inherited by a public template, you ship the entire backend JS bundle to anonymous users. Keep admin and site base templates strictly separate.
Extend via decoration or events, never by subclassing controllers. Controller internals are the least stable part of a UI bundle. If you must replace behaviour, decorate the service in services.yaml:
App\Admin\DecoratingFoo:
decorates: open_dxp.admin.foo
arguments: ['@.inner']
Debugging checklist when something breaks
bin/console debug:router | grep admin # routing resolved?
bin/console debug:event-dispatcher # is my subscriber registered?
bin/console debug:twig | grep -i admin # is the layout visible?
bin/console debug:container --tag=<menu-tag> # tagged services picked up?
Check var/log/dev.log for the underlying exception — a missing template, an unresolvable service argument, or a missing ROLE_ADMIN all surface as a generic 500/403 in the browser.
Treat the bundle as infrastructure, not application code. Put your admin customisations in a dedicated App\Admin namespace with its own controllers, templates, and subscribers. That keeps upgrades from the OpenDXP side to a composer update plus a diff review, rather than a merge conflict in a vendored file.
How can I help you explore Laravel packages today?