whatwedo/crud-bundle
whatwedo/crud-bundle generates full CRUD interfaces for your Doctrine entities from simple configuration. Scaffold list, create, edit, and delete views with built-in filtering, sorting, pagination, and access control, so you can ship admin backends and data management screens faster.
This is a Symfony bundle, not Laravel. The first decision is whether that matters. If you're on Laravel, read this assessment as a design reference (annotation/config-driven CRUD scaffolding) and implement the equivalent with Filament, Backpack, or a generic Route::resource + policy pair. The bundle itself will not run on Laravel.
If you are on Symfony and want to try it anyway:
Check installability before anything else. The last release was October 2017, which puts it in the Symfony 2.8/3.x era. Run:
composer require whatwedo/crud-bundle --dry-run
Then composer why-not whatwedo/crud-bundle when it fails. Expect to pin symfony/symfony or symfony/framework-bundle to ^3.4 and PHP to ^7.0|^7.1. Do not attempt this inside a Symfony 5/6 app; the removed Controller base class and annotation reader APIs will bite you.
Register the bundle the old way — add it to app/AppKernel.php's registerBundles() (pre-Flex) or config/bundles.php (if the package happens to be Flex-compatible; verify). Clear the cache afterward.
Read the source first — the docs are thin. With 9 stars and a single author, the README is a sketch. The real API surface is:
Annotation/ — this is the DSL. Whatever attributes exist (@Crud, @Field, etc.) define what the bundle can generate. Read these classes before writing a single entity.Controller/ — the base controller that does the dispatch. This is what you'll be extending or overriding.Resources/config/ — service definitions, and the routing loader (annotation-driven vs. YAML).Resources/views/ — the Twig templates you'll need to override for anything resembling real UI.First use case: one entity, read-only. Pick the simplest existing Doctrine entity in your app. Add the CRUD annotation to the class, clear cache, and bin/console debug:router | grep <entity> to confirm routes were generated. Hit the index route. That single loop — annotate → clear cache → inspect router → browse — is the entire feedback cycle for this bundle, and you should establish it before touching a second entity.
Annotation-driven scaffolding. The bundle's model is: your Doctrine entity carries metadata, the bundle reads it at container/route-compile time, and it emits a full CRUD controller with list, show, new, edit, and delete actions. This is the same shape as SensioGeneratorBundle's generate:doctrine:crud, but declarative rather than one-shot. The practical consequence: changing the shape of a form means editing an annotation on the entity, not editing a controller.
Prototype-then-eject. The sane workflow for a package in this state:
bin/console generate:doctrine:crud (or MakerBundle on 3.4) to materialize real controllers and templates, copy over the bundle's output as a starting point, and delete the dependency.Treat the bundle as a code generator you run continuously during prototyping, not as a runtime dependency you ship.
Layer business logic below it. The generated controller is a thin CRUD shell — it will not handle state machines, approval workflows, or cross-entity invariants. Put that in a domain service or a Doctrine event subscriber, and let the generated controller call into it via a small override. Do not stuff logic into the annotation; it can't hold it.
Template overrides are the primary customization point. Symfony bundle inheritance means you can override Resources/views/Crud/index.html.twig by placing a file at app/Resources/WhatwedoCrudBundle/views/Crud/index.html.twig (Symfony 3) or templates/bundles/WhatwedoCrudBundle/... (Symfony 4+). This is how you get a usable UI — the stock templates are bare. Budget most of your time here, not in PHP.
Pair it with a real form type. If the bundle auto-derives forms from entity metadata, custom validation constraints and PRE_SUBMIT listeners have nowhere to live. Bind an explicit FormType per entity and have the bundle use it, if the annotation supports naming one; otherwise accept that validation is limited to what the entity's own constraints express.
Security is yours, not the bundle's. There is no per-entity ACL, no voter integration, no ownership check. Wire the generated routes into your existing security.yml / security.yaml access_control by URL pattern, and add a voter for row-level checks inside a controller override. Generated delete endpoints are the classic hole.
It is effectively abandoned. Last release 2017, 0 dependents, 9 stars. There is no issue triage, no security response, no PHP 8 support. That is fine for a throwaway prototype on a pinned PHP 7.1 VM; it is disqualifying for anything you'll maintain.
Cache invalidation is mandatory after every annotation change. Routes and metadata are compiled. If a new field doesn't appear, you almost certainly forgot bin/console cache:clear. On Symfony 3 this is php app/console cache:clear --env=dev; also check var/cache/dev / app/cache/dev for stale container dumps.
Route collisions are silent. The generated route names follow a convention based on the entity's short name. Two entities named Item in different bundles/namespaces will overwrite each other and you'll get the wrong controller with no error. Inspect bin/console debug:router after adding each entity.
getDoctrine() and the base Controller class. If you're porting patterns to Symfony 5+, the bundle's inheritance from Symfony\Bundle\FrameworkBundle\Controller\Controller (or Controller in the older namespace) will not survive — that class was deprecated in 5.0 and removed in 6.0. Use AbstractController with constructor-injected EntityManagerInterface.
Debugging the generated output: bin/console debug:container --tag=controller.service_arguments, bin/console debug:router, and {{ dump() }} with the Twig debug bundle. Because the controller is generated, you cannot dd() inside it — override it in your app's src/Controller/ and break there instead. That override is also the cleanest way to add logging.
Extension points, ranked by usefulness: (1) Twig template overrides; (2) subclassing the base controller in your own bundle and pointing the annotation at your subclass, if supported; (3) Symfony service decoration on the bundle's services via config/services.yaml; (4) compiler passes. Options 3 and 4 require reading Resources/config/ closely — the service IDs are not documented anywhere.
Don't fight the form rendering. If the auto-generated form doesn't fit, override the template block rather than trying to bend annotations. The annotation vocabulary is small and you'll hit its ceiling within an hour; the Twig layer is unbounded.
Vendor the source if you ship it. composer.lock alone won't save you if Packagist metadata changes or the repo goes away. Given the maintenance status, consider committing a fork under your own VCS namespace — it's a few hundred lines and removes an existential dependency from a prototype you might actually demo.
How can I help you explore Laravel packages today?