This page explains how to interact with JavaScript from OCaml using the Js_of_ocaml library.
OCaml and JavaScript represent values differently, for example:
true/falseJs_of_ocaml (the lib) provides a typed interface to bridge these differences safely.
Other libraries for JavaScript interop:
'a Js.tAll 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 ##.
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?
Exception: OCaml integers can be used directlyβno conversion needed.
OCaml type | JS type | OCaml -> JS | JS -> OCaml |
| String | ||
| String | ||
| Boolean | ||
| Number | (direct) | (direct) |
| Number | ||
| Array |
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.bytestring and Js.to_bytestring:
let js_bytes = Js.bytestring "\x00\x01\x02"
let ocaml_bytes = Js.to_bytestring js_bytesOCaml 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_trueOCaml 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(* 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_arrTo 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
Type | Description |
| Read-only property |
| Write-only property |
| Read/write property |
| Method taking n arguments |
| Optional property (may be |
The PPX syntax rely on these info to provide type safe access to properties and method.
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
endWhen accessing a field using the ##./## syntax, the field name is transformed by:
This enables:
_Foo refers to JavaScript's Foo_type refers to JavaScript's typefoo_int and foo_string both refer to fooWarning: 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_.
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
endclass 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 __ : ..
endFor 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_Once you have described a JavaScript object's type, use the PPX syntax to access its properties and methods:
obj##.prop β read a propertyobj##.prop := v β write a propertyobj##meth args β call a methodGlobal 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 := xYou can also use Js.Unsafe.js_expr for any JavaScript expression:
let v = (Js.Unsafe.js_expr "window")##.documentBe careful: both Js.Unsafe.global and Js.Unsafe.js_expr are untyped. Verify the library documentation before writing type annotations.
When a property is missing from the OCaml interface, 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 := vJavaScript has two "missing value" types: null and undefined. Js_of_ocaml represents these with distinct types.
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.nullundefined)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.undefinedOCaml 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.
fun_callUse 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 |]meth_callUse 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 |]this bindingUse 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 |]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.
When JavaScript code needs to call back into OCaml (e.g., event handlers, async callbacks), you must wrap OCaml functions appropriately.
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.wrap_callback handles partial application: if JavaScript calls the function with fewer arguments than expected, the result is a partially applied function.
this bindingUse 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)For DOM events, use 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 Dom.full_handler:
let handler = Dom.full_handler (fun this event ->
let element : Dom_html.element Js.t = this in
(* ... *)
Js._true)For performance-critical code, or when you don't need partial application, use 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)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)JavaScript APIs vary across browsers. Use 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)##.someMethodJavaScript APIs often use union types (e.g., string | Node). Since OCaml requires a single type, use an opaque type with runtime checking.
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.nulltypeof for primitive typestype 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.nulltype 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 *)The 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.
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.
externalFor 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.
runtime_valueFor JavaScript objects, constants, or other non-function values, use Js.Unsafe.runtime_value:
//Provides: myConfig
var myConfig = { debug: true, version: 42 };let config : < debug : bool Js.t Js.prop; version : int Js.prop > Js.t =
Js.Unsafe.runtime_value "myConfig"Important: The argument must be a string literal, not a variable.
##., ##, new%js, object%js