Past JS interop
Archive of Dart's previous JS interop support.
The evolution of JavaScript interop
#Dart 3.3 introduces a new generation of JS interop that offers a unified set of features and APIs to access JavaScript and browser functionalities within your Dart code. This modern approach enhances the developer experience and enables WebAssembly (Wasm) support, aligning Dart with the future of the web.
The following table maps Dart's new JS and web interop solutions to their past counterparts:
| New interop libraries | Legacy libraries |
|---|---|
package:web |
dart:html
dart:indexed_db
dart:svg
dart:web_audio
dart:web_gl
|
dart:js_interop
dart:js_interop_unsafe
|
package:js
dart:js
dart:js_util
|
Previous iterations of JS interop for Dart are considered legacy and
are deprecated as of Dart 3.7 (Feb 2025).
Prefer using dart:js_interop
going forwards and
migrate usages of old interop libraries when possible.
package:web provides browser API support,
replacing legacy libraries such as dart:html.
Disallow deprecated JS interop libraries
#
To verify that your app and its dependencies don't import legacy web libraries
when compiling to JavaScript,
pass the --no-deprecated-js-interop flag to dart compile js
(dart2js)
or dart compile js-dev (dartdevc):
dart compile js --no-deprecated-js-interop -o out/main.js web/main.dart
If you build with webdev 4.1.0 or later
(with build_web_compilers
4.9.0 or later),
you can pass the --no-deprecated-js-interop flag to webdev
commands
such as webdev serve and webdev build.
To configure build.yaml directly instead,
set deprecated-js-interop: false under global_options
for all four build_web_compilers
builders
(build_web_compilers:ddc, build_web_compilers:ddc_modules,
build_web_compilers:dart2js_modules, and build_web_compilers:entrypoint).
Passing the --no-deprecated-js-interop flag enforces the following behavior:
-
Disallows legacy web imports: Importing any of the deprecated
dart:*web libraries (dart:html,dart:indexed_db,dart:js,dart:js_util,dart:svg,dart:web_audio, anddart:web_gl)—or packages that import them, such aspackage:js—results in a compile-time error. (dart compile wasmalways disallows these libraries and doesn't accept the--[no-]deprecated-js-interopflag.) -
Evaluates legacy conditional imports to
false: The environment conditionsdart.library.html,dart.library.indexed_db,dart.library.js,dart.library.js_util,dart.library.svg,dart.library.web_audio, anddart.library.web_glevaluate tofalsein conditional imports (such asif (dart.library.html)) andbool.fromEnvironment. -
Reports shortest-path import chains: When an app or a dependency imports a disallowed library,
dart compile js(dart2js) prints a deduplicated shortest-path import tree that pinpoints which library or package introduced the deprecated import:Deprecated JS interop libraries are imported through: main library ├── package:foo │ └── dart:html └── web/src/app.dart ├── package:foo (see above) └── package:bar └── dart:js_util
By default, JavaScript compilers allow deprecated JS interop libraries
(--deprecated-js-interop).
Pass the --no-deprecated-js-interop flag
in your continuous integration (CI) pipeline
to catch legacy web dependencies early
and prepare your code for the removal
of the deprecated dart:* web libraries from the Dart SDK.
To catch invalid type checks and casts with JS interop types,
enable the invalid_runtime_check_with_js_interop_types
lint rule.
Starting in package:lints
7.0.0,
package:lints/core.yaml includes this rule.
dart:js
#
dart:js exposed a concrete object wrapper
to interop with JS objects.
This wrapper contained String-based methods to dynamically get, set, and call
properties on the wrapped JS object. It was less performant due to wrapping
costs and ergonomically more difficult to use. For example, you did not get
code-completion as you couldn't declare interop members and instead relied on
Strings. Many of the functionalities exposed in dart:js like
allowInterop
were later re-exposed through other interop libraries.
This library has been legacy since
package:js and dart:js_util were released.
package:js
#
package:js introduced functionality to declare interop types and members.
It allowed users to write interop classes instead of interop extension types.
At runtime, these classes were erased to a type that is similar to
dart:js_interop's JSObject.
@JS()
class JSType {}
Users of package:js will find the
syntax and semantics of dart:js_interop familiar.
You might be able to migrate to dart:js_interop by
replacing the class definition with an extension type and
have it work in many cases.
There are significant differences, however:
-
package:jstypes could not be used to interop with browser APIs.dart:js_interoptypes can. -
package:jsallowed dynamic dispatch. This meant that if you cast thepackage:jstype todynamicand called an interop member on it, it would forward to the right member. This is no longer possible withdart:js_interop. -
package:js'@JShas no soundness guarantees as return types ofexternalmembers were not checked.dart:js_interopis sound. -
package:jstypes could not rename instance members or have non-externalmembers. -
package:jstypes could subtype and be a supertype of non-interop classes. This was often used for mocks. Withdart:js_interop, mocking is done by substituting the JS object instead. See the tutorial on mocking. -
@anonymoustypes were a way to declare an interop type with an object literal constructor.dart:js_interopdoesn't distinguish types this way and anyexternalnamed-argument constructor is an object literal constructor.
@staticInterop
#
Along with @JS and @anonymous, package:js later exposed
@staticInterop, which was a prototype of interop extension types. It is as
expressive and restrictive as dart:js_interop and was meant to be a
transitory syntax until extension types were available.
@staticInterop types were implicitly erased to JSObject. It required users
to declare all instance members in extensions so that only static semantics
could be used, and had stronger soundness guarantees. Users could use it to
interact with browser APIs, and it also allowed things like renaming and
non-external members. Like interop extension types, it didn't have support for
dynamic dispatch.
@staticInterop classes can almost always be migrated to an interop extension
type by just changing the class to an extension type and removing the
annotations.
dart:js_interop exposed @staticInterop (and @anonymous, but only if
@staticInterop is also used) to support static interop semantics until
extension types were added to the language. All such types should now be
migrated to extension types.
dart:js_util
#
dart:js_util supplied a number of utility functions that could not be
declared in an package:js type or were necessary for values to be passed back
and forth. This included members like:
-
allowInterop(which is nowFunction.toJS) -
getProperty/setProperty/callMethod/callConstructor(which are now indart:js_interop_unsafe) - Various JS operators
- Type-checking helpers
- Mocking support
- And more.
dart:js_interop and dart:js_interop_unsafe contain these helpers now with
possibly alternate syntax.
Unless stated otherwise, the documentation on this site reflects Dart 3.13.7. Page last updated on 2026-11-05. View source or report an issue.