symfony/webapp-pack
Symfony Flex metapackage that installs a curated set of bundles for building full web applications: framework, Twig, Doctrine ORM, security, forms, validator, mailer, console, and Maker. Use it to bootstrap a standard Symfony webapp with a single composer require.
symfony/webapp-pack is a Composer metapackage — it ships no PHP code. Installing it is just a shorthand for "pull in the standard set of bundles a Symfony web app needs, and let Flex run their recipes." The real work happens in the ~30 member packages and the config files they scaffold into config/.
Minimal install on a fresh skeleton:
composer create-project symfony/skeleton:"7.0.*" my-app
cd my-app
composer require webapp # Flex alias -> symfony/webapp-pack
On an existing skeleton: composer require symfony/webapp-pack. Answer yes to the Flex prompt about allow-plugins for symfony/flex and symfony/runtime if asked (or add them to composer.json up front — see Gotchas).
Where to look first:
composer show symfony/webapp-pack — the authoritative list of what you actually got.composer recipes — every recipe Flex applied, and symfony.lock — the manifest of those recipes. This is the file that explains "why does config/packages/security.yaml exist?"config/packages/ and config/routes.yaml — the scaffolded defaults you'll be editing.bin/console about and bin/console debug:config framework — confirm the kernel booted and bundles registered.First concrete use case: a Twig + form + validator + mailer CRUD page. The pack already includes twig-bundle, form, validator, security-bundle, mailer, asset-mapper, stimulus-bundle, ux-turbo, so you can go straight to bin/console make:controller (after adding Maker — see Gotchas) without touching Composer again. If you need Doctrine, layer orm-pack on top; if you're building an API, prefer api-pack instead of webapp-pack to avoid dragging in Twig/asset-mapper.
Compose packs, don't fight them. Packs are designed to stack. A typical webapp composer.json:
"require": {
"symfony/webapp-pack": "^7.0",
"symfony/orm-pack": "^2.4",
"symfony/security-bundle": "^7.0"
},
"require-dev": {
"symfony/maker-bundle": "^1.52",
"symfony/test-pack": "^1.1",
"phpstan/phpstan": "^1.10"
}
Flex dedupes recipes across packs, so requiring orm-pack after webapp-pack only adds the Doctrine scaffolding.
Trim what you don't use. The pack is deliberately broad. Removing unused members reduces attack surface and upgrade friction:
composer remove symfony/notifier symfony/webpack-encore-bundle
Flex will ask whether to delete the associated recipe files — answer carefully. Verify with composer why symfony/mime afterwards: mailer requires mime, but if you dropped mailer too, you may be able to drop more.
Pick a frontend strategy early. webapp-pack requires both symfony/asset-mapper and symfony/webpack-encore-bundle. That's a "choose your own adventure" default, not an endorsement of both. For new apps, keep AssetMapper (no Node, importmap-based) and remove Webpack Encore. For legacy SPA setups, do the reverse. Mixing them produces confusing assets.yaml vs webpack_encore.yaml resolution behavior.
Dev loop with generated code. Maker output assumes the pack's defaults (Twig, annotations/attributes routing, validator, serializer). Typical cycle:
bin/console make:entity Product
bin/console make:migration && bin/console doctrine:migrations:migrate
bin/console make:crud Product
bin/console debug:router
Because serializer, validator, and property-info are already present, #[Assert\...] attributes on entities and JSON responses via $this->json() work out of the box.
Email and notifications need DSNs. The recipes scaffold MAILER_DSN=null://null and NOTIFIER_DSN=null://null in .env. Nothing is sent until you set a real transport in .env.local. Wire async delivery through Messenger: MESSENGER_TRANSPORT_DSN in .env, then framework.messenger.routing in config/packages/messenger.yaml.
Commit both lock files. composer.lock pins versions; symfony.lock pins which Flex recipes ran. In CI:
composer install --no-interaction --no-progress --prefer-dist
bin/console cache:clear --env=test
vendor/bin/phpunit
Without symfony.lock Flex will re-run recipes non-deterministically, which is the #1 cause of "works on my machine" config drift in Flex projects.
Pin the Symfony minor centrally. Use extra.symfony.require in composer.json so all member packages track the same minor:
"extra": { "symfony": { "allow-contrib": false, "require": "7.0.*" } }
Then bump that string and composer update "symfony/*" when upgrading, reviewing every recipe diff Flex prints.
Build a house pack. If you standardize across projects, copy the pattern: a repo with "type": "metapackage" and a require block. That's literally all webapp-pack is.
It's a metapackage — there is no code to patch. File bugs against the specific member bundle (e.g. symfony/mailer), never against webapp-pack. The GitHub repo often has almost no issues because there's nothing to break.
composer remove symfony/webapp-pack is not "uninstall everything." It removes the metapackage and only the members nothing else requires. Orphaned packages stay in vendor/ and their config stays in config/packages/. Audit with composer why <package> and delete config/recipes manually.
allow-plugins will bite you in CI. Flex and Runtime are Composer plugins. A fresh composer.json without:
"config": { "allow-plugins": { "symfony/flex": true, "symfony/runtime": true } }
will fail composer install --no-interaction with a plugin-blocked error. Set it before committing.
No Doctrine, no PHPUnit, no Maker by default. webapp-pack is ORM- and test-agnostic. bin/console make:entity fails on a bare webapp install. Add orm-pack, --dev symfony/maker-bundle, symfony/test-pack explicitly. Don't assume the pack is "everything."
Silent mail is the classic first bug. MAILER_DSN=null://null is the scaffolded default. If $mailer->send() returns without error but nothing arrives, check config/packages/mailer.yaml and .env.local. Same story for Notifier.
symfony/runtime rewrites the front controller. public/index.php becomes:
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) { return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); };
Copying older front-controller boilerplate from tutorials will break under the pack.
Recipe conflicts on --force. Re-running composer recipes:install symfony/framework-bundle --force will overwrite your config/packages/framework.yaml. Diff before accepting; treat --force as destructive. In CI, never pass --force.
Check the pack version before upgrading Symfony. The last release visible here is Dec 2023 (Symfony 7.0 era). If you need 7.1/7.2 features, symfony/webapp-pack may lag; either pin the pack to the matching minor if a release exists, or composer require the individual bundles directly and drop the pack.
Two lock files, both meaningful. composer.lock for versions, symfony.lock for recipes. composer update changes one; only a recipe re-run changes the other. When config drifts mysteriously after a teammate's update, git diff symfony.lock is your first stop.
Debugging container/config errors after an upgrade: bin/console cache:clear, then inspect var/cache/dev/App_KernelDevDebugContainer.xml for the failing service reference. bin/console debug:config <bundle_alias> shows merged config (your YAML over recipe defaults); debug:container --parameters shows resolved env values.
Env layering: .env (committed defaults) → .env.local (gitignored, per-machine) → .env.$APP_ENV → .env.$APP_ENV.local. Use composer dump-env prod to precompile for production so .env files aren't read at runtime.
Extension points live in the bundles, not the pack. Customize behavior via AbstractBundle::prependExtension(), compiler passes, or service decoration. To swap the default set entirely, fork the pack's composer.json into your own metapackage and curate from there.
How can I help you explore Laravel packages today?