Getting Started
Generating Features
What you'll learn
- How the ephemeral config flow works
- Adding a new feature module to your monorepo
- Understanding what gets generated and why
- Checking how the feature registers with the host app
Prerequisites
- An existing Archipelago project (see Creating Your First Project)
Here is the whole flow for a feature called orders, from the command to the app wiring:
archipelago generate feature_monorepo_skeleton✓ Loaded brick: feature_monorepo_skeleton ┌──────────────────────────────────────────────────────────┐ │ Config file created: │ │ /home/dev/my_app/feature_monorepo_skeleton_config.json │ │ │ │ Edit the values and press Enter to │ │ continue generation... │ └──────────────────────────────────────────────────────────┘Config file created: /home/dev/my_app/feature_monorepo_skeleton_config.jsonEdit the values, then press Enter to continue.Keys starting with "@" are descriptions — they are stripped automatically.Press Enter when done editing...
import 'package:network_sdk_dio/network_sdk_dio.dart';import 'package:package_info_provider/package_info_provider.dart';// … ExternalModule(NetworkSdkDioPackageModule), ExternalModule(PackageInfoProviderPackageModule), // archipelago:feature_modules_before:end{ "featureName": "orders", "isShared": false, "hasBusinessLogic": true, "hasLocale": false, "includeGenerated": false, "stateManagement": "cubit", "prefix": "App"}How Ephemeral Config Works
When you generate a feature, Archipelago uses an ephemeral configuration flow:
- The CLI creates a temporary config template in the current directory
- You fill in the values (feature name, options) and save
- The brick is invoked with that config
- Generated files are written to your project
- The temporary config file is deleted
In CI, pass --config <file.json> instead and the interactive step is skipped. Nothing is stored in your project, so you never end up with stale config files.
Step 1: Generate a Feature
Run the generator from the project root. generate takes the exact brick slug:
archipelago generate feature_monorepo_skeletonThe config asks for:
- featureName: Used for package and class naming
- isShared:
truefor an_api/_implsplit (when other features consume it),falsefor a single package - hasBusinessLogic: Include repositories, use cases and local DI
- hasLocale: Give the feature its own slang translations
- isPlugin: Add native Android and iOS code (a Flutter plugin) with one
MethodChannel - stateManagement:
bloc,cubit(default),riverpod,providerornone - includeGenerated: Whether generated files are committed
Step 2: Review the Output
The feature always lands flat under features/. A single package (isShared: false) looks like:
features/payments/
├── lib/
│ ├── payments.dart # Barrel export
│ └── src/
│ ├── payments_manifest.dart # Routes, read at boot
│ ├── payments_sdk.dart # Port and lazy implementation
│ ├── data/ # if hasBusinessLogic
│ ├── di/ # if hasBusinessLogic
│ ├── domain/ # if hasBusinessLogic
│ ├── presentation/
│ └── router/
├── l10n/ # if hasLocale
├── slang.yaml # if hasLocale
├── android/, ios/ # if isPlugin
├── build.yaml
└── pubspec.yamlWith isShared: true you get two packages instead, features/payments_api/ (contract only) and features/payments_impl/ (implementation, DI, presentation, router).
Step 3: Register the Feature
Features register themselves. The brick adds the new packages to the workspace and the feature to the app pubspec.yaml. It registers the global module in the app injector.dart. You only resolve and generate:
dart pub get
melos run build # generate DI and routesThe feature's global module registers its FeatureManifest with FeatureSDKRegistry. The manifest adds its routes before the router is built. The SDK is a lazy singleton, built on first use. See Feature Manifests and SDKs.
Step 4: Wire Up Dependencies
If your feature depends on other SDKs, add them as workspace dependencies and let injectable resolve them:
dart run monorepo_toolkit deps internal payments_impl network_sdk_api// features/payments_impl/lib/src/data/payments_repository_impl.dart
@LazySingleton(as: PaymentsRepository)
class PaymentsRepositoryImpl implements PaymentsRepository {
PaymentsRepositoryImpl(this._network);
final HttpClient _network;
}With hasBusinessLogic: true these registrations go into the feature's own container (<feature>_container.dart). HttpClient comes from the app root, so forward it in <feature>_imports.dart: child.registerFactory<HttpClient>(() => root<HttpClient>());. See Dual-GetIt. Run flutter test test/di in the feature to check the wiring.
When to Use API/Impl Split
| Scenario | Use API Split? |
|---|---|
| Feature consumed by other features | Yes |
| You plan to swap the vendor implementation | Yes |
| Self-contained feature | No |
| Debug/release behavior difference | Use impl/noop instead |
Next Steps
Dive deeper into the architecture patterns in Understanding API/Impl Split.