Skip to content
Blog

Authoring Flutter Web Plugin Implementations with package:web and JS Interop

Learn how to build modern Web implementations for Flutter plugins using package:web, Wasm-compatible dart:js_interop, and Web APIs.

Published on • November 9, 2026

AI Assistant

With WebAssembly (Wasm) becoming the default target for Flutter Web, traditional legacy web interop libraries like dart:html and dart:js are deprecated. Modern Flutter Web plugins must be written using package:web and dart:js_interop.

This ensures your web plugin implementations compile cleanly to both JavaScript (dart2js) and WebAssembly (dart2wasm).

Key Differences: Legacy vs Modern Web Interop

  • Legacy (dart:html): Uses Dart-specific wrapper classes over DOM elements. Incompatible with WebAssembly because Wasm cannot directly manipulate Dart-wrapped JS objects without performance penalty.
  • Modern (package:web + dart:js_interop): Maps directly to standardized browser extension types. Compiles into fast, zero-cost JS/Wasm bindings.

Step 1: Adding Modern Web Dependencies

In your web plugin package (pubspec.yaml), add package:web and flutter_web_plugins:

dependencies:
  flutter:
    sdk: flutter
  flutter_web_plugins:
    sdk: flutter
  package:web: ^1.0.0
  my_plugin_platform_interface: ^1.0.0

Step 2: Registering the Web Plugin Entrypoint

Flutter Web uses static registerWith hooks to register web platform implementations at application startup:

lib/my_geo_plugin_web.dart:

import 'dart:js_interop';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:my_plugin_platform_interface/my_plugin_platform_interface.dart';
import 'package:web/web.dart' as web;

class MyGeoPluginWeb extends MyGeoPluginPlatform {
  /// Static registration hook invoked by Flutter Web plugin registrant
  static void registerWith(Registrar registrar) {
    MyGeoPluginPlatform.instance = MyGeoPluginWeb();
  }

  @override
  Future<bool> isGeolocationAvailable() async {
    // Access browser navigator object via package:web
    final navigator = web.window.navigator;
    return navigator.geolocation != null;
  }
}

Step 3: Wrapping External JavaScript Libraries with dart:js_interop

If your web plugin needs to call a third-party JavaScript library (such as Stripe.js, Chart.js, or a custom JS script), define extension types using @JS() annotations:

import 'dart:js_interop';

// Bind to global JavaScript function window.customNativeCryptoEncrypt(...)
@JS('customNativeCryptoEncrypt')
external JSString jsEncrypt(JSString payload, JSString secretKey);

// Bind to external JavaScript JS Object class
@JS('PaymentGateway')
extension type PaymentGateway._(JSObject _) implements JSObject {
  external factory PaymentGateway(JSString apiKey);

  external JSPromise<JSBoolean> processPayment(JSNumber amount);
}

Now call these external JS APIs from your Dart web plugin implementation:

class MyPaymentPluginWeb extends MyPaymentPlatform {
  @override
  Future<bool> chargeAmount(double amount, String apiKey) async {
    // Instantiates native JS PaymentGateway class
    final gateway = PaymentGateway(apiKey.toJS);

    // Call async JS promise and convert to Dart Future
    final JSBoolean result = await gateway.processPayment(amount.toJS).toDart;

    return result.toDart;
  }
}

Crucial Rules for Wasm-Compatible Web Plugins

  1. Never Import dart:html or dart:js: Importing legacy HTML libraries immediately breaks Wasm compilation (dart2wasm).
  2. Convert Types Explicitly: Use .toJS and .toDart extensions (e.g. string.toJS, jsString.toDart, future.toDart, promise.toJS) to cross the boundary cleanly.
  3. Handle Feature Detection: Always check if browser APIs exist before calling them (e.g. web.window.navigator.geolocation) to prevent runtime JS exceptions on unsupported browsers.

Takeaway: Using package:web and dart:js_interop guarantees that your Flutter Web plugins are performant, future-proof, and fully WebAssembly-ready.