Endorsed Federated Plugins in Flutter: implements, default_package, and Versioning
The pubspec mechanics behind federated plugins — the platforms map, the implements entry, default_package endorsement, dartPluginClass registration, and how to version three packages as one.
Published on • November 13, 2026
AI Assistant

flutter-plugin-platform-interface-pattern showed how to define the abstract interface and mock it in tests. This post is about the other half: the pubspec plumbing that makes three separate packages behave like one plugin — how a platform package declares which app-facing package it implements, how the app-facing package endorses it with default_package, and what versioning strategy keeps the trio compatible. Everything here comes from the official developing-packages guide.
The three packages, again
A package-separated federated plugin splits into:
- app-facing package — what app developers depend on (e.g.
hello); - platform interface package — the abstract contract (
hello_platform_interface); - platform implementation packages —
hello_windows,hello_web, one per platform.
The pubspec.yaml flutter.plugin.platforms map is where they’re wired together.
The app-facing pubspec
flutter:
plugin:
platforms:
android:
package: com.example.hello
pluginClass: HelloPlugin
ios:
pluginClass: HelloPlugin
windows:
default_package: hello_windows # endorsement lives here
Two entries do two different jobs. pluginClass means “this package contains native code for that platform” — the plugin is implemented right here. default_package: hello_windows means “the implementation for Windows lives in a dependency package,” and it only works because the app-facing package also declares hello_windows as a real dependency:
dependencies:
hello_windows: ^1.0.0
That’s the whole endorsement mechanism: a dependency plus a default_package pointer. When an app pulls in hello, pub resolves hello_windows automatically and the Windows implementation ships without the app author writing a line.
The platform package pubspec
A platform implementation points back with implements:
# pubspec.yaml of hello_windows
flutter:
plugin:
implements: hello
platforms:
windows:
pluginClass: HelloPluginWindows
implements: hello is the machine-readable claim “I satisfy the hello interface.” Without it, the tooling won’t recognize the package as a drop-in implementation, and endorsed resolution breaks.
The docs are explicit about the non-endorsed path too: if the original author won’t take your package as a dependency, users add it manually —
dependencies:
hello: ^1.0.0
hello_windows: ^1.0.0 # non-endorsed implementation
— which also works as an override when you need to replace an endorsed implementation with your own fork.
Dart-only platforms: dartPluginClass
Web and Dart-FFI implementations have no native pluginClass. They register themselves from Dart:
flutter:
plugin:
implements: hello
platforms:
web:
pluginClass: HelloPluginWeb
fileName: hello_web.dart
The class exposes a static registerWith() that sets the platform interface instance — the same token-verified instance setter covered in the platform-interface post. A platform can even be hybrid: declare both dartPluginClass and pluginClass and the startup runs both registrations. And if iOS and macOS share a codebase, sharedDarwinSource: true lets both platforms read from a single darwin/ folder instead of duplicated ios/ and macos/ trees.
Versioning three packages as one
Nothing in the tooling versions your interface for you — that’s policy. The patterns that hold up:
- The interface package is the compatibility contract. Additive methods with default implementations (or extension methods) are minor bumps; removing or tightening a signature is major, and it forces majors across every platform package at once.
- Platform packages declare a floor, not a ceiling.
hello_windowsdepends onhello_platform_interface: ^1.2.0so old app-facing packages still resolve, while apps get fixes bypub upgrade. - Release in lockstep on breaking changes. Publish the interface first, then platform packages against it, then the app-facing package that pins the new minimums — in that order, so no window exists where pub can resolve an incompatible pair.
- Test the contract, not the implementation. Run each platform package’s tests against the interface’s
MockPlatformInterfaceMixinfakes; run the app-facing package’s tests against a fake interface. Neither side needs a device.
When not to federate
Federation pays off when platforms diverge — a web implementation in Dart, native code elsewhere, independent release cycles. If every platform is one thin MethodChannel over identical native code, a single plugin package is less ceremony for the same result, and FFI packages (the recommended approach for pure native libraries since Flutter 3.38) skip channels entirely.
Takeaway: Wire federated plugins with implements: hello on each platform package and default_package plus a real dependency on the app-facing side, register Dart-only platforms via dartPluginClass/registerWith(), and treat the interface package as the single versioned contract the whole trio releases against.