Fix 'output-dir option is required' in Flutter l10n
You pulled the latest stable Flutter SDK, ran flutter run, and your app — which built fine last week — now dies before it even compiles:
User defined the synthetic-package option to false but did not
specify an output directory. The output-dir option is required
when synthetic-package is false.
Nothing in your Dart changed. What changed is Flutter. The synthetic package:flutter_gen that used to hold your generated AppLocalizations has been removed, and the gen-l10n tool now refuses to guess where to put the generated code. This is not a bug in your project — it's the final step of a deprecation that started back in November 2024. The good news: fixing it is a three-line config edit and one find-and-replace. Here's exactly what to change.
What actually broke
For years, flutter gen-l10n wrote your generated localization classes into a synthetic package at .dart_tool/flutter_gen/gen_l10n/. You never saw those files — they lived in a virtual package you imported like this:
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
That approach caused a lot of pain: broken IDE navigation, confused analyzers, incompatibility with Dart's explicit-package-dependencies and pub workspaces, and code you couldn't open or read. So the Flutter team deprecated it. The rollout happened in stages:
- Nov 2024 — deprecation announced;
synthetic-package: falsebecame the recommended setting. - Flutter 3.32 (2025 stable) — the synthetic package stopped being generated by default.
generate: trueinpubspec.yamlbecame required. - 2026 stable —
package:flutter_gensupport is fully removed. The tool no longer creates a synthetic package or edits yourpackage_config.jsonat all.
Once flutter_gen is removed, gen-l10n has only one place to write: your lib/ source tree. But it won't invent a directory for you — hence the error. flutter synthetic-package false without an output-dir is now an incomplete configuration.
The 3-line fix: minimal l10n.yaml
Open l10n.yaml in your project root (create it next to pubspec.yaml if you don't have one). The three lines that resolve the error are synthetic-package: false, an explicit output-dir, and — recommended for a clean migration — nullable-getter: false. Here's a complete, correct file:
# l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
# The three lines that fix the breaking change:
synthetic-package: false # stop using package:flutter_gen
output-dir: lib/l10n # where AppLocalizations now lands
nullable-getter: false # AppLocalizations.of(context) is non-null
A few notes so you don't trip on the details:
output-dircan equalarb-dir. Putting the generated Dart right next to the.arbfiles (both inlib/l10n) is the most common, tidy layout. You can also send them elsewhere, e.g.output-dir: lib/src/generated/l10n.nullable-getter: falsemakesAppLocalizations.of(context)return a non-nullable instance, so you writeAppLocalizations.of(context).titleinstead ofAppLocalizations.of(context)!.title. It's optional, but if you're editing this file anyway, flip it now and delete a pile of!operators.output-classandoutput-localization-filedefault toAppLocalizationsandapp_localizations.dart. They're shown here for clarity; keep whatever names you already use.
Then make sure your pubspec.yaml still opts into generation — this has been required since 3.32:
# pubspec.yaml
flutter:
generate: true
Where the generated AppLocalizations file lands now
Before, the file was invisible inside .dart_tool/. Now, after you run:
flutter gen-l10n
you get real, committable source files at your output-dir:
lib/l10n/
├── app_en.arb # your source strings
├── app_es.arb
├── app_localizations.dart # generated: base class + delegate
├── app_localizations_en.dart # generated: English
└── app_localizations_es.dart # generated: Spanish
These are ordinary Dart files. You can open them, cmd-click into them, and — importantly — decide whether to commit them. Most teams add the generated app_localizations*.dart files to .gitignore and regenerate on build (they're produced automatically by flutter run/flutter build when generate: true). Either choice is fine; just be consistent across the team.
Migrate every import in one command
The last step is the one that touches the most files. Every import 'package:flutter_gen/...' is now a dead reference and will fail to resolve. You need to point them at your real package path. The new import is package:<your_package_name>/<output-dir-under-lib>/app_localizations.dart. If your package is named my_app (check the name: field in pubspec.yaml) and output-dir: lib/l10n, the import becomes:
import 'package:my_app/l10n/app_localizations.dart';
Instead of editing dozens of files by hand, do a project-wide find/replace. On macOS/BSD:
grep -rl 'package:flutter_gen/gen_l10n' lib test \
| xargs sed -i '' 's|package:flutter_gen/gen_l10n/app_localizations.dart|package:my_app/l10n/app_localizations.dart|g'
On Linux (GNU sed), drop the empty quotes after -i:
grep -rl 'package:flutter_gen/gen_l10n' lib test \
| xargs sed -i 's|package:flutter_gen/gen_l10n/app_localizations.dart|package:my_app/l10n/app_localizations.dart|g'
Replace my_app with your actual package name and l10n with your output-dir path relative to lib/. If you import other generated files (rare), repeat for those paths.
Verify the migration
Run these in order and you should be back to green:
flutter clean
flutter pub get
flutter gen-l10n # regenerates into output-dir
flutter analyze # no unresolved flutter_gen imports
flutter run
If flutter analyze still flags a missing flutter_gen import, you have a stray reference the grep missed — search again for flutter_gen across the whole repo, including any code under packages/ in a monorepo or workspace.
Common follow-on errors
- "Attempted to generate localizations code without having the flutter: generate flag turned on." — Add
generate: trueunderflutter:inpubspec.yaml. - Using pub workspaces? With
synthetic-package: falseand workspaces you may needexplicit-package-dependenciesenabled; the non-synthetic path is exactly what makes workspaces work, so this is the direction you want anyway. AppLocalizations.of(context)now needs a!— you leftnullable-getterat its default (true). Set it tofalseand regenerate, or add the null-assertion.
Turn a scary breaking change into a config you understand
The reason this error feels alarming is that gen-l10n used to hide everything. Moving generation into your source tree is a net win: readable code, working navigation, and a config file you can reason about. If you want the full field-by-field reference for tuning this file — deferred loading, headers, untranslated-message reports — see our complete l10n.yaml configuration guide.
Managing the .arb files those generated classes read from is its own chore once you pass two or three locales. FlutterLocalisation gives you a visual ARB editor so you edit app_<locale>.arb translations in a UI instead of hand-diffing JSON, plus ICU plural-syntax validation that flags a locale missing a plural category its language actually requires (a dropped few/many in Arabic, Polish, or Russian). Once your build is green again, keep it that way.
Try FlutterLocalisation free and manage your Flutter translations without touching raw ARB by hand.