Skip to content
Blog

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:

  1. 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.
  2. Platform packages declare a floor, not a ceiling. hello_windows depends on hello_platform_interface: ^1.2.0 so old app-facing packages still resolve, while apps get fixes by pub upgrade.
  3. 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.
  4. Test the contract, not the implementation. Run each platform package’s tests against the interface’s MockPlatformInterfaceMixin fakes; 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.

Sources