JS types
                    Dart values and JS values belong to separate language domains. When compiling to
                    Wasm, they execute in separate runtimes as well. As such, you should treat JS
                    values as foreign types. To provide Dart types for JS values,
                    dart:js_interop
                     exposes a set of types prefixed with JS called "JS types".
                    These types are used to distinguish between Dart values and JS values at
                    compile-time.
                  
                    Importantly, these types are reified differently based on whether you compile to
                    Wasm or JS. This means that their runtime type will differ, and therefore you
                    can't use is checks and as casts.
                    In order to interact with and examine these JS values, you should use
                    external interop members or 
                    conversions.
                  
Type hierarchy
#JS types form a natural type hierarchy:
- 
                      Top type: 
JSAny, which is any non-nullish JS value- Primitives: 
JSNumber,JSBoolean,JSString,JSSymbol,JSBigInt JSObject, which is any JS objectJSFunctionJSExportedDartFunction, which represents a Dart callback that was converted to a JS function
JSArrayJSPromiseJSDataViewJSTypedArray- JS typed arrays like 
JSUint8Array 
- JS typed arrays like 
 JSBoxedDartObject, which allows users to box and pass Dart values opaquely within the same Dart runtime- From Dart 3.4 onwards, the type 
ExternalDartReferenceindart:js_interopalso allows users to pass Dart values opaquely, but is not a JS type. Learn more about the tradeoffs between each option here. 
- From Dart 3.4 onwards, the type 
 
 - Primitives: 
 
                    You can find the definition of each type in the dart:js_interop API docs.
                  
Conversions
#
                    To use a value from one domain to another, you will likely want to convert the
                    value to the corresponding type of the other domain. For example, you may want
                    to convert a Dart List<JSString> into a JS array of strings, which is
                    represented by the JS type JSArray<JSString>, so that you can pass the array
                    to a JS interop API.
                  
Dart supplies a number of conversion members on various Dart types and JS types to convert the values between the domains for you.
Members that convert values from Dart to JS usually start with toJS:
String str = 'hello world';
JSString jsStr = str.toJS;
                      
                      
                      
                    Members that convert values from JS to Dart usually start with toDart:
JSNumber jsNum = ...;
int integer = jsNum.toDartInt;
                      
                      
                      
                    Not all JS types have a conversion, and not all Dart types have a conversion. Generally, the conversion table looks like the following:
dart:js_interop type | Dart type | 
|---|---|
JSNumber, JSBoolean, JSString | 
                          num, int, double, bool, String | 
                        
JSExportedDartFunction | Function | 
JSArray<T extends JSAny?> | 
                          List<T extends JSAny?> | 
                        
JSPromise<T extends JSAny?> | 
                          Future<T extends JSAny?> | 
                        
Typed arrays like JSUint8Array | 
                          Typed lists from dart:typed_data | 
                        
JSBoxedDartObject | Opaque Dart value | 
ExternalDartReference | Opaque Dart value | 
Requirements on external declarations and Function.toJS
                    #
                  
                    In order to ensure type safety and consistency, the compiler places requirements
                    on what types can flow into and out of JS. Passing arbitrary Dart values into JS
                    is not allowed. Instead, the compiler requires users to use a compatible interop
                    type, ExternalDartReference, or a primitive, which would then be implicitly
                    converted by the compiler. For example, these would be allowed:
                  
@JS()
external void primitives(String a, int b, double c, num d, bool e);
                      
                      
                      
                    @JS()
external JSArray jsTypes(JSObject _, JSString __);
                      
                      
                      
                    extension type InteropType(JSObject _) implements JSObject {}
@JS()
external InteropType get interopType;
                      
                      
                      
                    @JS()
external void externalDartReference(ExternalDartReference _);
                      
                      
                      
                    Whereas these would return an error:
@JS()
external Function get function;
                      
                      
                      
                    @JS()
external set list(List _);
                      
                      
                      
                    
                    These same requirements exist when you use Function.toJS
                     to make a Dart
                    function callable in JS. The values that flow into and out of this callback must
                    be a compatible interop type or a primitive.
                  
                    If you use a Dart primitive like String, an implicit conversion happens in the
                    compiler to convert that value from a JS value to a Dart value. If performance
                    is critical and you don’t need to examine the contents of the string, then using
                    JSString instead to avoid the conversion cost may make sense like in the
                    second example.
                  
Compatibility, type checks, and casts
#
                    The runtime type of JS types may differ based on the compiler. This affects
                    runtime type-checking and casts. Therefore, almost always avoid is checks
                    where the value is an interop type or where the target type is an interop type:
                  
void f(JSAny a) {
  if (a is String) { … }
}
                      
                      
                      
                    void f(JSAny a) {
  if (a is JSObject) { … }
}
                      
                      
                      
                    Also, avoid casts between Dart types and interop types:
void f(JSString s) {
  s as String;
}
                      
                      
                      
                    
                    To type-check a JS value, use an interop member like typeofEquals
                     or
                    instanceOfString
                     that examines the JS value itself:
                  
void f(JSAny a) {
  // Here `a` is verified to be a JS function, so the cast is okay.
  if (a.typeofEquals('function')) {
    a as JSFunction;
  }
}
                      
                      
                      
                    
                    From Dart 3.4 onwards, you can use the isA
                     helper function to check whether
                    a value is any interop type:
                  
void f(JSAny a) {
  if (a.isA<JSString>()) {} // `typeofEquals('string')`
  if (a.isA<JSArray>()) {} // `instanceOfString('Array')`
  if (a.isA<CustomInteropType>()) {} // `instanceOfString('CustomInteropType')`
}
                      
                      
                      
                    Depending on the type parameter, it'll transform the call into the appropriate type-check for that type.
Dart may add lints to make runtime checks with JS interop types easier to avoid. See issue #4841 for more details.
null vs undefined
                    #
                  
                    JS has both a null and an undefined value. This is in contrast with Dart,
                    which only has null. In order to make JS values more ergonomic to use, if an
                    interop member were to return either JS null or undefined, the compiler maps
                    these values to Dart null. Therefore a member like value
                     in the following
                    example can be interpreted as returning a JS object, JS null, or undefined:
                  
@JS()
external JSObject? get value;
                      
                      
                      
                    
                    If the return type was not declared as nullable, then the program will throw an
                    error if the value returned was JS null or undefined to ensure soundness.
                  
JSBoxedDartObject vs ExternalDartReference
                    #
                  
                    From Dart 3.4 onwards, both JSBoxedDartObject
                     and ExternalDartReference
                    
                    can be used to pass opaque references to Dart Objects through JavaScript.
                    However, JSBoxedDartObject wraps the opaque reference in a JavaScript object,
                    while ExternalDartReference is the reference itself and therefore is not a JS
                    type.
                  
                    Use JSBoxedDartObject if you need a JS type or if you need extra checks to
                    make sure Dart values don't get passed to another Dart runtime. For example, if
                    the Dart object needs to be placed in a JSArray or passed to an API that
                    accepts a JSAny, use JSBoxedDartObject. Use ExternalDartReference
                    
                    otherwise as it will be faster.
                  
                    See toExternalReference
                     and toDartObject
                     to convert to and from an
                    ExternalDartReference.
                  
Unless stated otherwise, the documentation on this site reflects Dart 3.9.2. Page last updated on 2025-8-27. View source or report an issue.