picoss/sonata-extra-admin-bundle
A Symfony bundle that adds practical extensions and utilities to Sonata Admin, helping developers customize admin panels with less boilerplate. It complements existing Sonata setups with reusable admin tools, templates, and helpers for faster backend development.
Reality check first: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/app.php entry, no php artisan vendor:publish. If you are a Laravel developer reading this because it appeared in a PHP package list, the only day-to-day leverage you get is pattern borrowing (fluent exporters, sortable list columns, dashboard blocks) — you will not install this into a Laravel app. Everything below assumes a Symfony + Sonata Admin codebase, or a legacy hybrid. Treat "how do I use this in Laravel" as out of scope; use Filament, Nova, or a Livewire table instead.
For a Symfony/Sonata developer, the honest first step is confirm it installs against your Sonata version before you read any docs. With 18 stars, zero dependents, and no visible release cadence, the version matrix is the whole decision. Spin a throwaway branch:
composer require picoss/sonata-extra-admin-bundle --dry-run
If Composer resolves cleanly against your current sonata-project/admin-bundle constraints, continue. If it fights them, stop — the cost of a fork will exceed the value of the conveniences.
Where to look first, in order:
composer.json — which Symfony/Sonata majors does it pin? This tells you if it is even alive on your stack.DependencyInjection/Configuration.php — what is actually configurable under the sonata_extra_admin namespace (exporter formats, default page size, block registration).Resources/config/services.yml — the service IDs and interfaces you will inject or implement: Exporter, PositionType, ExportableAdminInterface, SortableInterface, AdminBlockInterface (names vary by version — read the actual file).Register the bundle (Symfony 4+ config/bundles.php, Symfony 3 AppKernel):
Picoss\SonataExtraAdminBundle\SonataExtraAdminBundle::class => ['all' => true],
First use case — add CSV export to an existing admin. This is the highest-value, lowest-risk feature and the best smoke test that the bundle loaded:
class ProductAdmin extends AbstractAdmin implements ExportableAdminInterface
{
protected function configureExportFields(Exporter $exporter): void
{
$exporter
->setFormats(['csv', 'xls'])
->add('id')
->add('name')
->add('price', 'price', ['format' => '%.2f']);
}
}
If ?_export=csv on the list page produces a file, the bundle is wired correctly and you can evaluate the rest.
Export as a decorator on Admin, not controller logic. The bundle's design is: implement ExportableAdminInterface, and the Exporter service builds a field list via a fluent add($field, $label, $options) chain. Treat it like League\Csv\Writer — a builder you configure per admin, not per request. Centralize in a base admin so every screen gets consistent behaviour:
abstract class BaseAdmin extends AbstractAdmin implements ExportableAdminInterface
{
protected array $exportFormats = ['csv'];
public function getExportFormats(): array
{
return $this->exportFormats;
}
public function getExportFields(): array
{
return array_map(fn ($m) => $m->getName(), $this->getList()->getElements());
}
}
Then ProductAdmin extends BaseAdmin and only overrides configureExportFields() when a screen needs custom columns or formatting. This keeps the surface area small and makes the eventual fork trivial to reason about.
Sortable entities are a three-piece wiring job. There is no single "sortable" switch. The pattern across every version is:
position column to the entity (migration required).SortableInterface on the entity — typically just getPosition()/setPosition().PositionType on the admin's list fields and point the sortable controller/route at the entity.// ProductAdmin::configureListFields
$listMapper->add('position', PositionType::class, ['sortable' => true]);
Admin blocks follow an interface contract. Blocks are registered as services tagged for the bundle and implement AdminBlockInterface (or a similar contract — read the shipped interfaces). Each block renders independently, so a failing block degrades one dashboard tile rather than the whole page. This is the most reusable pattern to steal if you later fork: define a block interface, tag services, render each in isolation.
Integration tips that pay off day-to-day:
BaseAdmin early. Every admin extending it gets export and consistent list behaviour, and you only pay the Sonata wiring once.configureListFields conditionally adds the PositionType) so you can disable the bundle in one place if it breaks.Version compatibility is the real gotcha. The bundle targets an older Symfony/Sonata generation. Composer constraints are the most likely failure, and they will fail silently until you actually run a page. If composer require succeeds but admin list pages throw ServiceNotFoundException, check that the bundle's services.yml names match the Sonata version you are on — Sonata renames services between majors. Diff the file against your vendor/sonata-project/admin-bundle service aliases.
?_export=csv is the smoke test. If the query parameter does nothing, either (a) the admin does not implement ExportableAdminInterface, (b) the Exporter service is not wired, or (c) the bundle did not register. Enable Symfony's container debug (bin/console debug:container | grep -i export) to confirm the service exists before touching your admin class.
The exporter only knows about configureListFields. Fields added via configureFormFields or show are invisible to the exporter. If a column is missing from your export, it is not in the list mapper — either add it there or add it explicitly in configureExportFields().
position needs a deterministic initial value or your first drag breaks sort order. Seed the column with sequential values in the migration (UPDATE product SET position = id or a row-number update) before registering PositionType. Otherwise all rows share 0 and the drag handler produces arbitrary ordering.
Sortable lists and pagination are coupled. Reordering only the current page is fine; reordering across pages is not supported by the drag handler. If your use case needs cross-page ordering, that is a build-your-own, not a configure-the-bundle.
The bundle is effectively unmaintained. Treat it as a fork-and-own dependency from day one. With 18 stars, no CI, no tests, and a single low-activity maintainer, plan for:
composer.json, not a caret range.repositories entry pointing at your fork, so upstream disappearance does not block a release.Keep it out of customer-facing and regulated surfaces. This is admin-tool glue: an unpatched bundle in the render path of an admin screen that surfaces PII, payments, or audit data is a compliance liability. Internal dashboards only, and preferably read-mostly.
Do not let the "PHP package" label fool a Laravel evaluation. If a stakeholder asks "can we use this in our Laravel app?" the answer is no — it would mean adopting Symfony and Sonata, which is a migration, not a dependency. The correct fallback on Laravel is to reimplement the two patterns you actually want (a fluent exporter and a sortable list) in Filament or a Livewire table in an afternoon.
When it stops earning its keep, fork the specific feature you use. If you only ever use the exporter, the fork is one service class and a Twig template. That is cheaper to own than the whole bundle, and it removes the version-matrix question permanently.
How can I help you explore Laravel packages today?