Federated Plugin Architecture: How Flutter Plugins Split Across Platforms
A Flutter plugin no longer has to be one package. Federated plugins split the API, the platform interface, and the native implementations — here is the architecture and when to use it.
Published on • October 13, 2026
AI Assistant

When you depend on a Flutter plugin like shared_preferences or url_launcher, you pull in a single pub package name. But behind that name sits not one package but three or more: an app-facing API, a platform interface, and separate native implementations per platform. This is the federated plugin architecture, and understanding it changes how you both consume and author plugins.
The problem with a single-package plugin
In the original model, one package contained the Dart API, the Android code, the iOS code, and the method-channel plumbing all together. That works until:
- A platform you do not target still gets its native code shipped in your build.
- A community member wants to add web or desktop support — they must either get commit access to your repo or fork it.
- A team wants to swap the implementation (say, a different camera backend) without changing the API their app imports.
Federation solves all three by separating concerns into independently versioned packages.
The three layers
1. App-facing package — the package users pubspec.yaml depends on. It contains the public Dart API and, crucially, no platform code. Its flutter.plugin.platforms entry declares which implementation packages are used, which is how “endorsed” implementations work.
2. Platform interface package — a Dart-only package that defines the contract. It typically contains:
- An abstract class (often extending
PlatformInterfacefromplugin_platform_interface) with the API surface. - A token-verification mechanism (
PlatformInterface.verifyToken) so only the registered implementation can claim to implement it. - A default (usually
UnimplementedError) implementation.
abstract class GeoPlatform extends PlatformInterface {
GeoPlatform() : super(token: _token);
static final Object _token = Object();
static GeoPlatform _instance = MethodChannelGeo();
static GeoPlatform get instance => _instance;
static set instance(GeoPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<LatLng> getCurrentPosition() {
throw UnimplementedError('getCurrentPosition() has not been implemented.');
}
}
3. Platform implementation packages — one package per platform (geo_android, geo_ios, geo_web), each registering itself as GeoPlatform.instance in its Dart entry point. The app-facing package then just calls GeoPlatform.instance.getCurrentPosition().
Endorsed vs. unendorsed implementations
An implementation is endorsed when the app-facing package lists it in its own pubspec.yaml. Users get the right implementation automatically just by depending on the main package — this is why url_launcher “just works” on web after a version bump, with no changes to your code.
An unendorsed (community or alternative) implementation is published separately; you opt in by adding it as a direct dependency in your pubspec.yaml. Its Dart plugin registrant then overrides the default at registration time. This is the escape hatch: anyone can ship geo_web without touching the core repo.
The Dart plugin registrant
For pure-Dart platforms (web, desktop), Flutter generates a dart_plugin_registrant.dart at build time from the dartPluginClass declarations in every package’s pubspec. Each implementation package declares:
flutter:
plugin:
platforms:
web:
pluginClass: GeoWeb
fileName: geo_web.dart
At app startup the registrant calls GeoWeb.registerWith(Registrar.instance), which sets the platform interface’s singleton. On mobile, native registrants (the old method-channel path) do the equivalent from Kotlin/Swift.
When should you federate?
You do not need this for every plugin. The official guidance in the Flutter plugin development guide is pragmatic:
- Start as a simple plugin. One package, method channels, done. Most plugins never need more.
- Federate when you add web or desktop support — a strong signal, because pure-Dart platforms benefit from trimming native code.
- Federate when there is genuine implementation competition — e.g., different mapping providers behind one API, or a community pushing an implementation the core team will not maintain.
- Federate for large platform-specific APIs where independent release cadence matters (e.g.,
camera_android,camera_avfoundationshipped fixes without blocking the umbrella release).
The cost is real: more packages to version, publish, and keep compatible via the interface package. The interface package becomes your semver contract — breaking changes there break everyone.
The dependency rule
One invariant holds the whole system together: the app-facing package and implementation packages both depend on the platform interface, and never on each other. Implementations register; the interface mediates. If you ever find geo_ios importing geo, the architecture is inverted.
Further reading
- Developing packages and plugins — Flutter docs — the canonical guide, including federated plugin structure and endorsement
- plugin_platform_interface on pub.dev — the token-verification base class
- endorsed federated plugin example — how endorsement is declared in pubspec
Federation is one of those Flutter architectures you benefit from knowing even if you never author a plugin: it explains why plugin changelogs mention implementation packages you never installed, and why adding url_launcher_web to your pubspec is sometimes exactly the wrong fix — the platform you need is probably already endorsed.