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_strFor 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_bytesBooleans
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_trueNumbers
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_numArrays
(* 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_arrDescribing 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
endMethod and property name mangling
When accessing a field using the ##./## syntax, the field name is transformed by:
- Removing a leading underscore (if present)
- 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
endFull 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 __ : ..
endBinding 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 := xYou can also use Js_of_ocaml.Js.Unsafe.js_expr for any JavaScript expression:
let v = (Js.Unsafe.js_expr "window")##.documentBe 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 := vHandling 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.nullJs.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.undefinedCalling 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!") 1000Js_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 := handlerFor 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)##.someMethodUnion 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.nullUsing 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.nullConverting 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 jsonFor 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
- PPX syntax - Syntax reference: ##., ##, new%js, object%js
- Exporting to JavaScript - Make OCaml code callable from JavaScript
- Js_of_ocaml.Js - Core JavaScript bindings
- Js_of_ocaml.Dom_html - HTML DOM bindings