symfony/flex
Symfony Flex helps developers create Symfony applications, from the simplest micro-style projects to complex ones with dozens of dependencies. It streamlines project setup and automates package configuration so you can start building faster with less boilerplate.
Symfony Flex is a Composer plugin, not a library — it changes how Composer behaves, so it only matters when you're consuming Symfony packages (or recipes) inside a project. For a Laravel-first developer, that means it's relevant in three scenarios: (a) you're adding Symfony components/bundles to a Laravel app, (b) you maintain a Symfony service alongside a Laravel app, or (c) you want recipes to auto-wire something like Mercure or Mailer.
Minimum steps:
symfony/flex:^1.21).composer require symfony/flex for a Symfony-style project; composer require --dev symfony/flex is usually the right call for a Laravel hybrid, because recipes are a build-time concern and you don't want Flex resolving in --no-dev production installs."config": { "allow-plugins": { "symfony/flex": true } }
Without this, Composer silently skips the plugin and nothing gets configured — the single most common "why didn't it work" issue.symfony.lock — the ledger of which recipes were applied at which version. Commit it.composer.json → extra.symfony — the control surface (allow-contrib, require, endpoint). Empty/absent in a default Laravel app.git diff after any install — this is what a recipe actually did.composer require symfony/mailer or symfony/mercure-bundle, and observe the recipe: .env entries, config/packages/*.yaml, and possibly composer.json scripts. Then run composer recipes to see the installed recipe list and composer recipes:install symfony/mailer --force to re-apply if you want to re-roll a file.The day-to-day mental model: Flex is a scaffolder that runs once per package per recipe version, recorded in symfony.lock. Once applied, the files are yours; Flex stays out of the way until you ask it to update.
Let recipes write the boilerplate, then own it. The idiomatic flow is composer require <pkg> → review git diff → commit. Recipe files (config/packages/foo.yaml, .env vars) are normal project files afterwards. You're expected to edit them; Flex only re-touches them via explicit composer recipes:update, which performs a 3-way merge.
Pin the Symfony version set once. Add to composer.json:
"extra": {
"symfony": {
"require": "7.2.*",
"allow-contrib": true
}
}
This constrains all auto-added Symfony dependencies to a single minor line and is how recipes:update and transitive Symfony installs stay consistent. Override it in a CI matrix with the SYMFONY_REQUIRE environment variable — useful when you want to smoke-test a project against several Symfony minors without editing the file.
Opt into community recipes explicitly. Bundles outside the official symfony/recipes repo (e.g. doctrine/doctrine-bundle, api-platform/core) only apply their recipes if extra.symfony.allow-contrib: true is set (or, as of v2.11, the SYMFONY_ALLOW_CONTRIB=1 env var). Otherwise their recipes are silently skipped and you'll spend an hour wondering why nothing appeared.
Run a private recipe server for internal bundles. extra.symfony.endpoint accepts an array of indexes (highest priority first) — point it at your own index.json before GitHub so your org's recipes take precedence and are reviewed internally. This is the standard pattern for teams shipping shared Symfony bundles.
Use composer recipes:update as your dependency-refresh workflow. When bumping a Symfony package, run composer recipes:update symfony/framework-bundle (or the interactive no-arg form) to merge the new recipe against your local edits. It relies on Git under the hood to compute the diff — commit or stash first, and expect it to leave temp files when it can't auto-merge. --no-changelog (v2.11) skips the changelog prompt in automation.
Wire auto-scripts into CI deliberately. extra.symfony.auto-scripts (typically cache:clear, assets:install, importmap:install) fire on every install/update. In CI, do composer install --no-scripts --no-interaction, then run composer auto-scripts as a distinct, observable step — this makes failures attributable and keeps builds deterministic.
Dump the environment for production. With symfony/dotenv present, composer symfony:dump-env prod compiles .env* files into .env.local.php, which is faster and matches Symfony conventions. Rare in a pure Laravel app, but useful when sharing Laravel-shaped deployments across a hybrid stack.
Laravel hybrid pattern — inspect, don't adopt blindly. The safest integration when pulling a Symfony bundle into Laravel is:
composer require symfony/<bundle> — let Flex apply the recipe in a scratch Symfony project or a branch.git show / composer recipes:install symfony/<bundle> to see exactly what files the recipe produces.config/ and .env manually, then remove or disable Flex.Using a pack (symfony/webapp-pack, symfony/api-pack) makes sense if you're standing up a Symfony service inside a Laravel shop — one composer require symfony/api-pack gives you the standard bundle set for that service without re-learning each recipe.
Removal is best-effort — verify. composer remove <pkg> triggers Flex's unconfigure pass: it deletes files the recipe created if unmodified, and strips matching .env keys. Anything you edited survives (v2.7.1 fixed a bug where still-referenced files were wrongly removed). Always re-inspect git diff after a removal.
allow-plugins. On Composer 2.2+, Flex in require but not in config.allow-plugins results in zero recipe application, no error. In --no-interaction CI this is guaranteed. Add the entry to composer.json explicitly.--no-scripts; safest is composer require symfony/flex on a clean tree first.config/packages/framework.yaml), Flex aborts that recipe and leaves symfony.lock without the entry — so a later composer install tries again. Use composer recipes:install <pkg> --force to overwrite (v2.6 added --yes for the prompt-all case).--no-scripts disables both recipes and auto-scripts. They're driven by the same event. If you use it in CI, remember recipes won't apply either — so never run composer update --no-scripts and commit the resulting lock without also applying recipes in a controlled step.allow-contrib. Confirming the earlier pattern: no flag = no recipe = no .env vars = "it installed but nothing was configured". v2.11's SYMFONY_ALLOW_CONTRIB=1 env var is convenient for ephemeral CI.symfony.lock is not composer.lock. Commit both. symfony.lock records recipe identity and version; deletion forces re-application on next install, which can clobber config in a fresh checkout.composer recipes:update needs a Git repo and a clean tree. It also runs git hooks unless blocked (v2.6 suppresses them); in CI set --no-interaction and expect conflicts to leave .recipe sidecar files that must be resolved and deleted manually.doctrine/doctrine-bundle can append a database: service block to docker-compose.yml, or add entries to Makefile; a recipe can also mutate composer.json scripts. These have had their own regressions (duplicate top-level keys, indentation on help targets, jsonPath handling in the ComposerScripts configurator). Scan the full git diff, not just config/.bin/console. In a Laravel project where a Symfony recipe added cache:clear / assets:install, every composer install will fail loudly. Either strip extra.symfony.auto-scripts after inspecting, or install Flex under --no-scripts and copy the recipe output by hand.SYMFONY_REQUIRE env > extra.symfony.require > *. When Flex adds a Symfony dependency to satisfy a recipe, it uses this constraint, so a mismatched extra.symfony.require will silently hold packages back.composer recipes:install <pkg> -vvv — the log shows the endpoint URL and HTTP status. Private recipe servers behind GitHub need a token; Flex forwards GITHUB_TOKEN/COMPOSER_AUTH for raw.githubusercontent.com since v2.1.6.symfony/mailer from 7.1 to 7.2 does not touch its recipe — only composer recipes:update does. Plan recipe updates as a separate, reviewable PR.--yes, --no-changelog, and SYMFONY_ALLOW_CONTRIB are 2.x-only. Pin explicitly if you rely on those flags.How can I help you explore Laravel packages today?