Warning: Reason support is experimental. We are looking for beta-tester and contributors.

JavaScript interoperability

This page explains how to interact with JavaScript from OCaml using the Js_of_ocaml library.

Introduction

OCaml and JavaScript represent values differently, for example:

  • OCaml strings are sequence of bytes; JavaScript strings are UTF-16
  • OCaml booleans are integers (0/1); JavaScript has distinct true/false
  • OCaml arrays have a tag element; JavaScript arrays don't
  • OCaml objects don't map to JavaScript objects

Js_of_ocaml (the lib) provides a typed interface to bridge these differences safely.

Alternatives

Other libraries for JavaScript interop:

  • Brr — Alternative browser API bindings with a different design
  • gen_js_api — Generates bindings from TypeScript definitions

Core concept: 'a Js.t

All JavaScript values have type 'a Js.t where the phantom type parameter 'a encodes the shape of the JavaScript value:

For example, Js.js_string Js.t represents a JavaScript string, and < length : int Js.readonly_prop > Js.t represents an object with a length property.

To work with these values (access properties, call methods), you need the PPX syntax extension which provides operators like ##. and ##.

Conversions

OCaml and JavaScript represent basic types differently (see runtime representation). For example, OCaml strings are byte sequences while JavaScript strings are UTF-16. When passing values between OCaml and JavaScript code, you must convert them explicitly.

The conversion functions follow a consistent naming pattern:

  • Js.xxx converts OCaml to JavaScript (e.g., Js.string, Js.bool, Js.array)
  • Js.to_xxx converts JavaScript to OCaml (e.g., Js.to_string, Js.to_bool, Js.to_array)

When do you need to convert?

  • Passing OCaml values to JavaScript functions or properties
  • Reading JavaScript values back into OCaml
  • Working with DOM APIs (which use JavaScript types)

Exception: OCaml integers can be used directly—no conversion needed.

Summary

OCaml type JS type OCaml -> JS JS -> OCaml
string String Js_of_ocaml.Js.string Js_of_ocaml.Js.to_string
string (bytes) String Js_of_ocaml.Js.bytestring Js_of_ocaml.Js.to_bytestring
bool Boolean Js_of_ocaml.Js.bool Js_of_ocaml.Js.to_bool
int Number (direct) (direct)
float Number Js_of_ocaml.Js.float Js_of_ocaml.Js.to_float
'a array Array Js_of_ocaml.Js.array Js_of_ocaml.Js.to_array

Strings

OCaml strings are byte arrays; JavaScript strings are UTF-16.

(* OCaml string -> JS string *)
let js_str : Js.js_string Js.t = Js.string "Hello"

(* JS string -> OCaml string *)
let ocaml_str : string = Js.to_string js_str

For binary data (bytes 0-255), use Js_of_ocaml.Js.bytestring and Js_of_ocaml.Js.to_bytestring:

let js_bytes = Js.bytestring "\x00\x01\x02"
let ocaml_bytes = Js.to_bytestring js_bytes

Booleans

OCaml booleans are encoded as 0 and 1, not JavaScript's true and false.

let js_true : bool Js.t = Js._true        (* or Js.bool true *)
let js_false : bool Js.t = Js._false      (* or Js.bool false *)
let ocaml_bool : bool = Js.to_bool js_true

Numbers

OCaml integers can be used directly. Floats need conversion.

(* Floats need conversion *)
let js_num : Js.number Js.t = Js.float 3.14
let ocaml_float : float = Js.to_float js_num

Arrays

(* OCaml array -> JS array *)
let js_arr : int Js.js_array Js.t = Js.array [| 1; 2; 3 |]

(* JS array -> OCaml array *)
let ocaml_arr : int array = Js.to_array js_arr

Describing JS objects

To call methods or access properties on a JavaScript object, you need to tell OCaml what shape the object has. This is done using OCaml class types, where each method declaration describes either a JavaScript property or method.

There are two ways to write these types:

Inline (anonymous) types — useful for one-off or simple cases:

< field1 : type1; field2 : type2; ... > Js.t

Named class types — better for reusable or complex interfaces:

class type myObject = object
  method field1 : type1
  method field2 : type2
end

(* Use as: myObject Js.t *)

For instance, a JavaScript object with a data property and an appendData method would have type:

< data : Js.js_string Js.t Js.prop;
  appendData : Js.js_string Js.t -> unit Js.meth > Js.t

Property types

Type Description
'a Js.readonly_prop Read-only property
'a Js.writeonly_prop Write-only property
'a Js.prop Read/write property
t1 -> ... -> tn -> t Js.meth Method taking n arguments
'a Js.optdef_prop Optional property (may be undefined)

The PPX syntax rely on these info to provide type safe access to properties and method.

Example

Given a JavaScript object:

{
  name: "example",           // read-only string
  count: 42,                 // read-write number
  greet: function(x) { ... } // method
}

Its type could be myObj Js.t:

class type myObj = object
  method name : Js.js_string Js.t Js.readonly_prop
  method count : int Js.prop
  method greet : Js.js_string Js.t -> unit Js.meth
end

Method and property name mangling

When accessing a field using the ##./## syntax, the field name is transformed by:

  1. Removing a leading underscore (if present)
  2. Removing all characters starting from the last underscore

This enables:

  • Capitalized names: _Foo refers to JavaScript's Foo
  • Reserved ocaml keywords: _type refers to JavaScript's type
  • Method overloading: foo_int and foo_string both refer to foo

Warning: This mangling is a common source of bugs. If you write obj##.some_property, it accesses the JavaScript property some (not some_property). To access some_property, use obj##._some_property_ or obj##.some_property_.

Examples

class type canvas = object
  (* All three refer to the same JS method: drawImage *)
  method drawImage :
      imageElement Js.t -> int -> int -> unit Js.meth
  method drawImage_withSize :
      imageElement Js.t -> int -> int -> int -> int -> unit Js.meth
  method drawImage_fromCanvas :
      canvasElement Js.t -> int -> int -> unit Js.meth
end

Full naming rules

class type example = object
  (* All of these refer to JS field [meth] *)
  method meth : ..
  method meth_int : ..
  method _meth_ : ..
  method _meth_aa : ..

  (* All of these refer to JS field [meth_a] *)
  method meth_a_int : ..
  method _meth_a_ : ..
  method _meth_a_b : ..

  (* Refer to [Meth] (capitalized) *)
  method _Meth : ..

  (* Refer to [_meth] (leading underscore in JS) *)
  method __meth : ..

  (* Refer to [_] *)
  method __ : ..
end

Binding constants from a class

For JavaScript constants like SomeLib.SomeClass.VALUE_A:

(* Type definition *)
class type someClass = object
  method _VALUE_A_ : int Js.readonly_prop
  method _VALUE_B_ : int Js.readonly_prop
end

(* Get the class object *)
let someClass : someClass Js.t =
  (Js.Unsafe.js_expr "SomeLib")##._SomeClass

(* Access constants *)
let value_a = someClass##._VALUE_A_

Accessing JavaScript values

Once you have described a JavaScript object's type, use the PPX syntax to access its properties and methods:

  • obj##.prop — read a property
  • obj##.prop := v — write a property
  • obj##meth args — call a method

Global variables

Global JavaScript variables are properties of the global object. Use Js_of_ocaml.Js.Unsafe.global to access them (window in browsers, globalThis in Node.js):

(* Access document *)
let doc : Dom_html.document Js.t = Js.Unsafe.global##.document

(* Read and write a custom global *)
let get_config () : config Js.t = Js.Unsafe.global##.myAppConfig
let set_config (x : config Js.t) = Js.Unsafe.global##.myAppConfig := x

You can also use Js_of_ocaml.Js.Unsafe.js_expr for any JavaScript expression:

let v = (Js.Unsafe.js_expr "window")##.document

Be careful: both Js_of_ocaml.Js.Unsafe.global and Js_of_ocaml.Js.Unsafe.js_expr are untyped. Verify the library documentation before writing type annotations.

Untyped property access

When a property is missing from the OCaml interface, use Js_of_ocaml.Js.Unsafe.coerce for untyped access:

(* Read a property *)
let value = (Js.Unsafe.coerce obj)##.someProp

(* Write a property *)
(Js.Unsafe.coerce obj)##.someProp := v

Handling null and undefined

JavaScript has two "missing value" types: null and undefined. Js_of_ocaml represents these with distinct types.

Js.Opt for nullable values (null)

Use Js_of_ocaml.Js.Opt for values that may be null (e.g., DOM methods that return null when an element is not found):

(* Check if null *)
let is_present = Js.Opt.test value

(* Convert to OCaml option *)
let opt : element Js.t option = Js.Opt.to_option value

(* Handle both cases *)
let result = Js.Opt.case value
  (fun () -> (* null *) "not found")
  (fun v -> (* has value *) process v)

(* Get value or raise exception *)
let v = Js.Opt.get value (fun () -> failwith "was null")

(* Create nullable values *)
let some_val : element Js.t Js.opt = Js.some element
let null_val : element Js.t Js.opt = Js.null

Js.Optdef for optional values (undefined)

Use Js_of_ocaml.Js.Optdef for values that may be undefined (e.g., optional object properties, array access beyond bounds):

(* Check if defined *)
let is_defined = Js.Optdef.test value

(* Convert to OCaml option *)
let opt : config Js.t option = Js.Optdef.to_option value

(* Handle both cases *)
let result = Js.Optdef.case value
  (fun () -> (* undefined *) default_config)
  (fun v -> (* defined *) v)

(* Create optdef values *)
let def_val : int Js.optdef = Js.def 42
let undef_val : int Js.optdef = Js.undefined

Calling JavaScript functions

OCaml and Javascript do not follow the same calling convention. In OCaml, functions can be partially applied, returning a function closure. In Javascript, when only some of the parameters are passed, the others are set to the undefined value. As a consequence, it is not possible to call a Javascript function from OCaml as if it was an OCaml function, and conversely.

There are three ways to call JavaScript functions, depending on how this should be bound.

At the moment, there is no syntactic sugar for calling Javascript functions.

Standalone functions with fun_call

Use Js_of_ocaml.Js.Unsafe.fun_call for functions where this doesn't matter:

(* Equivalent to: decodeURI(s) *)
let decodeURI (s : Js.js_string Js.t) : Js.js_string Js.t =
  Js.Unsafe.fun_call
    (Js.Unsafe.js_expr "decodeURI")
    [| Js.Unsafe.inject s |]

(* Equivalent to: parseInt(s, 10) *)
let parseInt (s : Js.js_string Js.t) : int =
  Js.Unsafe.fun_call
    (Js.Unsafe.js_expr "parseInt")
    [| Js.Unsafe.inject s; Js.Unsafe.inject 10 |]

Methods with meth_call

Use Js_of_ocaml.Js.Unsafe.meth_call to call a method on an object (this is bound to the object):

(* Equivalent to: arr.slice(1, 3) *)
let slice arr start stop =
  Js.Unsafe.meth_call arr "slice"
    [| Js.Unsafe.inject start; Js.Unsafe.inject stop |]

Functions with explicit this binding

Use Js_of_ocaml.Js.Unsafe.call when you need to explicitly set this:

(* Equivalent to: func.call(thisArg, arg1, arg2) *)
let result =
  Js.Unsafe.call func thisArg [| Js.Unsafe.inject arg1; Js.Unsafe.inject arg2 |]

Calling runtime primitives

For JavaScript functions declared in runtime files (with //Provides:), you can use OCaml external declarations:

external my_primitive : int -> int -> int = "my_js_function"

This calls the JavaScript function my_js_function directly, without the overhead of Js.Unsafe wrappers. See writing JavaScript primitives for how to define such functions.

Passing OCaml functions to JavaScript

When JavaScript code needs to call back into OCaml (e.g., event handlers, async callbacks), you must wrap OCaml functions appropriately.

Basic callbacks with Js.wrap_callback

(* setTimeout example *)
let set_timeout f ms =
  Js.Unsafe.global##setTimeout
    (Js.wrap_callback f)
    ms

let () = set_timeout (fun () -> print_endline "Hello!") 1000

Js_of_ocaml.Js.wrap_callback handles partial application: if JavaScript calls the function with fewer arguments than expected, the result is a partially applied function.

Callbacks with this binding

Use Js_of_ocaml.Js.wrap_meth_callback when the callback needs access to this:

(* The first parameter receives the 'this' value *)
let callback = Js.wrap_meth_callback (fun this event ->
  let target : Dom_html.element Js.t = this in
  (* ... handle event ... *)
  Js._true)

DOM event handlers

For DOM events, use Js_of_ocaml.Dom_html.handler which wraps the function and handles the return value (Js._false prevents the default action):

let handler = Dom_html.handler (fun event ->
  Dom_html.window##alert (Js.string "Clicked!");
  Js._true)

button##.onclick := handler

For handlers that need access to this, use Js_of_ocaml.Dom.full_handler:

let handler = Dom.full_handler (fun this event ->
  let element : Dom_html.element Js.t = this in
  (* ... *)
  Js._true)

Strict arity callbacks

For performance-critical code, or when you don't need partial application, use Js_of_ocaml.Js.Unsafe.callback:

(* Strict callback - missing args become undefined, extra args are lost *)
let strict_cb = Js.Unsafe.callback (fun x y -> x + y)

(* Explicit arity - ensures exactly 2 arguments *)
let arity_cb = Js.Unsafe.callback_with_arity 2 (fun x y -> x + y)

Callbacks with variable arguments

Use Js_of_ocaml.Js.Unsafe.callback_with_arguments when the callback receives a variable number of arguments:

let varargs_cb = Js.Unsafe.callback_with_arguments (fun args ->
  let len = args##.length in
  Printf.printf "Called with %d arguments\n" len)

Feature detection

JavaScript APIs vary across browsers. Use Js_of_ocaml.Js.Optdef to check for optional features:

let supports_fetch () =
  Js.Optdef.test Js.Unsafe.global##.fetch

let supports_local_storage () =
  Js.Optdef.test Js.Unsafe.global##.localStorage

(* Safely access optional members *)
let get_optional_method obj =
  Js.Optdef.to_option (Js.Unsafe.coerce obj)##.someMethod

Union types

JavaScript APIs often use union types (e.g., string | Node). Since OCaml requires a single type, use an opaque type with runtime checking.

Using instanceof for object types

(* Opaque type representing the union: Element | Text *)
type element_or_text

class type container = object
  method child : element_or_text Js.t Js.prop
end

(* Cast using instanceof *)
let as_element (x : element_or_text Js.t) : Dom.element Js.t Js.opt =
  if Js.instanceof x (Js.Unsafe.global##._Element)
  then Js.some (Js.Unsafe.coerce x)
  else Js.null

Using typeof for primitive types

type string_or_number

let as_string (x : string_or_number Js.t) : Js.js_string Js.t Js.opt =
  if Js.typeof x = Js.string "string"
  then Js.some (Js.Unsafe.coerce x)
  else Js.null

Converting to OCaml variants

type child =
  | Element of Dom.element Js.t
  | Text of Dom.text Js.t

let classify_child (x : element_or_text Js.t) : child =
  if Js.instanceof x (Js.Unsafe.global##._Element)
  then Element (Js.Unsafe.coerce x)
  else Text (Js.Unsafe.coerce x)

(* Now use pattern matching *)
let handle container =
  match classify_child container##.child with
  | Element e -> (* work with element *)
  | Text t -> (* work with text node *)

JSON serialization

The Js_of_ocaml.Json module provides serialization between OCaml values and JSON strings. The deserialization is unsafe in the same way the OCaml Marshal.from_string function is.

(* OCaml value -> JSON string *)
let json : Js.js_string Js.t = Json.output value

(* JSON string -> OCaml value (unsafe, like Marshal) *)
let value : 'a = Json.unsafe_input json

For type-safe JSON handling, use ppx_deriving_json.

Accessing runtime values

JavaScript values declared with //Provides: in runtime files can be accessed from OCaml. There are two approaches depending on whether you're accessing a function or a non-function value.

See writing JavaScript primitives for more about the //Provides: syntax.

Functions: use external

For JavaScript functions, use OCaml's external declaration:

//Provides: my_add
function my_add(x, y) { return x + y; }
external my_add : int -> int -> int = "my_add"

let result = my_add 1 2  (* calls the JS function directly *)

This is efficient and integrates naturally with OCaml code.

Non-function values: use runtime_value

For JavaScript objects, constants, or other non-function values, use Jsoo_runtime.Js.runtime_value:

//Provides: myConfig
var myConfig = { debug: true, version: 42 };
let config : < debug : bool Js.t Js.prop; version : int Js.prop > Js.t =
  Jsoo_runtime.Js.runtime_value "myConfig"

Important: The argument must be a string literal, not a variable.

See also