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

How to bind a JS library for OCaml

Accessing JavaScript properties

Global variables

Global JavaScript variables are properties of the global object. Use 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.Unsafe.js_expr for any JavaScript expression:

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

Be careful: both Js.Unsafe.global and 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 (e.g., dynamically added by a library), use 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

For type-safe access, define a class type for the extended object or wrap the unsafe access in getter/setter functions.

Binding a JS object

Use OCaml class types to describe the shape of JavaScript objects. Each method declaration represents either a property or a method of the JS object.

Property types

Type Access OCaml usage
t readonly_prop read only obj##.prop
t writeonly_prop write only obj##.prop := v
t prop read/write both
t1 -> t2 -> ... -> t meth method obj##meth arg1 arg2
t optdef_prop optional read obj##.prop returns t Optdef.t

Example

Given a JavaScript object:

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

Bind it as:

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 name mangling

OCaml method names are transformed to JavaScript names using underscore conventions. When accessing a field of an object, the name given in OCaml is transformed by: 1. Removing a leading underscore (if present) 2. Removing all characters starting from the last underscore

This enables: - Capitalized names: Use _Foo to refer to JavaScript's Foo - Reserved words: Use _type to refer to JavaScript's type - Method overloading: Use foo_int and foo_string for the same foo method

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

Type conversions

OCaml and JavaScript have different representations for basic types. Use these functions to convert between them.

Strings

OCaml strings and JavaScript strings have different encodings. Use Js.string and Js.to_string for UTF-8 encoded OCaml strings:

(* 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.bytestring and Js.to_bytestring:

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

Booleans

JavaScript booleans are not OCaml booleans. Use Js.bool and Js.to_bool:

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. For floats, use Js.float and Js.to_float:

(* Integers work directly *)
let count : int = obj##.count

(* Floats need conversion *)
let js_num : Js.number Js.t = Js.number_of_float 3.14
let ocaml_float : float = Js.float_of_number 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

Summary table

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

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.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.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

When to use which

Scenario Type Example
DOM lookup Js.Opt getElementById returns null if not found
Optional property Js.Optdef Property may not exist on object
Array access Js.Optdef Js.array_get returns undefined for missing index
API returns null Js.Opt Many DOM APIs use null for "no value"
Feature detection Js.Optdef Check if method/property exists

Combining with option types

Both modules provide to_option for idiomatic OCaml code:

let find_element id =
  Dom_html.getElementById_opt id
  |> Js.Opt.to_option

let () =
  match find_element "myId" with
  | Some el -> (* use element *)
  | None -> (* not found *)

Calling JS functions

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

Standalone functions with fun_call

Use 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.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.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 |]

See the Js_of_ocaml.Js.Unsafe module API for more details.

Passing OCaml functions to JavaScript

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

Basic callbacks with Js.wrap_callback

Use Js.wrap_callback to wrap an OCaml function for JavaScript:

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

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

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

Callbacks with this binding

Use 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)

Strict arity callbacks

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

(* 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)

DOM event handlers

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

let button = Dom_html.getElementById "myButton"

let handler = Dom_html.handler (fun event ->
  Dom_html.window##alert (Js.string "Clicked!");
  Js._true (* return false to prevent default *))

button##.onclick := handler

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

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

Callbacks with variable arguments

Use 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)

Using a JS constructor

To call a JavaScript constructor (i.e., new F(...)), first obtain the constructor (i.e. from the global object), then use new%js to instantiate it.

Basic usage

(* In .ml: get the constructor *)
let dateConstr : (int -> int -> int -> Js.date Js.t) Js.constr =
  Js.Unsafe.global##._Date

(* Create an instance using new%js *)
let my_date = new%js dateConstr 2024 0 15

This is equivalent to:

new Date(2024, 0, 15)

Handling overloaded constructors

JavaScript constructors often accept different argument types. Since OCaml requires fixed types, define multiple bindings for each signature:

(* In .mli *)
val dateConstr : (int -> int -> int -> Js.date Js.t) Js.constr
val dateConstr_fromValue : (int -> Js.date Js.t) Js.constr
val dateConstr_fromString : (Js.js_string Js.t -> Js.date Js.t) Js.constr

(* In .ml - all point to the same JS constructor *)
let dateConstr = Js.Unsafe.global##._Date
let dateConstr_fromValue = Js.Unsafe.global##._Date
let dateConstr_fromString = Js.Unsafe.global##._Date

Constructors from nested objects

For constructors not on the global object, bind them first:

let workerConstr : (Js.js_string Js.t -> worker Js.t) Js.constr =
  Js.Unsafe.global##._Worker

(* For nested: someLib.SomeClass *)
let someClass = (Js.Unsafe.js_expr "someLib")##._SomeClass in
new%js someClass arg1 arg2

Constructing JS objects manually

To construct a JS object without calling a constructor, use the Ppx syntax extension (recommended) or Js.Unsafe.obj.

Using the PPX syntax (recommended)

let options = object%js
  val x = 3 (* read-only prop *)
  val mutable y = 4 (* read/write prop *)
  method greet name = Js.string ("Hello " ^ Js.to_string name)
end

This produces the equivalent of:

{ x: 3, y: 4, greet: function(name) { return "Hello " + name; } }

See the Ppx documentation for more details.

Using Js.Unsafe.obj

For dynamic object construction, use Js.Unsafe.obj with an array of key-value pairs:

let options : < x: int Js.prop; y: int Js.prop > Js.t =
  Js.Unsafe.obj [|
    ("x", Js.Unsafe.inject 3);
    ("y", Js.Unsafe.inject 4);
  |]

A common pattern is to create an empty object and set properties afterwards. This is useful when calling JavaScript functions that expect an options object with optional fields. Use writeonly_prop for the type to prevent reading unset properties (which would return undefined):

class type myOptions = object
  method timeout : int Js.writeonly_prop
  method retry : bool Js.t Js.writeonly_prop
end

let call_with_options () =
  let options : myOptions Js.t = Js.Unsafe.obj [||] in
  options##.timeout := 5000;
  options##.retry := Js._true;
  some_js_function options

Check availability of methods and properties

JavaScript APIs vary across browsers and environments. A method or property may exist in one browser but not another, or may have been added in a recent version. Use Js.Optdef to safely check for and access optional members.

Checking if a member exists

let has_method obj = Js.Optdef.test (Js.Unsafe.coerce obj)##.someMethod
let has_prop obj = Js.Optdef.test (Js.Unsafe.coerce obj)##.someProp

Safely accessing optional members

Use Js.Optdef.to_option to convert to an OCaml option:

let get_optional_method obj =
  Js.Optdef.to_option (Js.Unsafe.coerce obj)##.someMethod

let () =
  match get_optional_method my_obj with
  | Some f -> (* use the method *)
  | None -> (* fallback behavior *)

Use Js.Optdef.case for inline handling:

let result =
  Js.Optdef.case (Js.Unsafe.coerce obj)##.optionalMethod
    (fun () -> (* not defined, use fallback *) default_value)
    (fun m -> (* defined, use it *) m arg1 arg2)

Example: feature detection

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

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

Object property with multiple types

JavaScript APIs often use union types where a property can hold values of different types (e.g., string | Node or number | null). Since OCaml requires a single type, use an opaque type with runtime type checking.

Using instanceof for object types

Use Js.instanceof to distinguish between different object types (e.g., DOM nodes, custom classes). Define an opaque type and cast functions:

(* 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 functions 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

let as_text (x : element_or_text Js.t) : Dom.text Js.t Js.opt =
  if Js.instanceof x (Js.Unsafe.global##._Text)
  then Js.some (Js.Unsafe.coerce x)
  else Js.null

(* Usage *)
let handle_child (container : container Js.t) =
  let child = container##.child in
  Js.Opt.case (as_element child)
    (fun () ->
      Js.Opt.case (as_text child)
        (fun () -> failwith "unexpected type")
        (fun text -> (* handle text node *)))
    (fun elem -> (* handle element *))

Using typeof for primitive types

For primitive types (string, number, boolean), use Js.typeof:

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

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

Converting to OCaml variants

For cleaner OCaml code, convert the union to a variant:

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 *)

Accessing JavaScript runtime values

The Jsoo_runtime.Js.runtime_value function allows direct access to JavaScript values provided by the runtime (declared with //Provides: directives in JavaScript files).

external runtime_value : string -> 'a = "caml_jsoo_runtime_value"

This function is useful when you need to access JavaScript values defined by an external javascript package. and you don't want to polute the global object.

Important: The argument must be a string literal, not a variable. The compiler will reject non-literal arguments.

Example

Given a JavaScript file custom.js linked with your program:

//Provides: process
var process = "my process value";

//Provides: myConfig
var myConfig = { debug: true, version: 42 };

You can access these values from OCaml:

let process_str : Js.js_string Js.t = Jsoo_runtime.Js.runtime_value "process"

let config : < debug : bool Js.t Js.prop; version : int Js.prop > Js.t =
  Jsoo_runtime.Js.runtime_value "myConfig"

let () =
  print_endline (Js.to_string process_str);
  if Js.to_bool config##.debug then
    Printf.printf "Version: %d\n" config##.version