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

Eliom Camlp4 syntax extension

Eliom uses syntax extensions to make the development of client-server applications easier.

In order to use these syntax extensions, use the file extension .eliom for the implementation and .eliomi for the corresponding interface. By using this naming conventions, eliomc and js_of_eliom will use Eliom's syntax extension automatically.

We provide two different syntax extensions, one based on PPX (which we recommend) and an older one based on Camlp4. This manual page documents our Camlp4 syntax extension.

Table of contents

Server, client, and shared-sections

Server, client, and shared-sections make possible to write the code of the server and client parts in the same source file, and also to defined sections of the code that are common to the two sides. Some special brackets make possible to distinguish between client and server code:


or no brackets for server side code,


for client side code, and


for some code that is common to client and server parts.

This means that the corresponding sections are compiled only for the server and/or the client program.

Injecting server values into the client code

Structuring the source code of your application into client and server sections wouldn't be of much use as it; we need a possibility to exchange data between those parts in a way which reflects the asymmetries between the client and the server parts, i.e. that the client program is sent and initialized by the server program.

Thus to ease the direct exchange of values between server and client, it is possible to access any server side, top level variable in a subsequent client section by just prefixing it with a %-sign. This is called injecting a server value into the client:

  let v = Random.int 100
  ... %v ... (* Usage of server side [v] on the client /
                injection of [v] *)

The server side value is injected once and for all when the page is sent for the first time. There is no automatic request to the server when the client tries to access this value. In particular, if the server side value is mutable, a copy is sent to the client.

Server side client values

On the other hand, it is possible to declare and deal with arbitrary client values in the server program. Those are just arbitrary expressions of client-side code declared inside double curly braces:

  ... {typ{ expr }} ...

If expr has type typ on the client, the resulting client value has the type typ client_value which is abstract on the server. But once it is sent to the client, it evaluates to the value of expr having type typ.

In some cases where inference is not sufficient, eliomc will emits a warning indicating than this type annotation is needed.


Again, variables from the context of the client value may be injected to it by prefixing them by a %-sign. This is for example reasonable to inject a parameter into a request client value:

  let () = My_app.register_service ~path ~get_params:Eliom_parameter.(string "name")
    (fun name () ->
       ignore {unit{ Eliom_lib.alert "Hello %s!" %name }};
       Lwt.return html)

The pattern used in this example is very common to ask the client side program to execute a piece of code after receiving the page.


Server side client values may occur in a request position, i.e. they are created during the processing of a request:

let _ = My_app.register_service ~path ~get_params
    (fun _ _ ->
       let v = {Js.string{ Js.string "another client side string" }} in
       Lwt.return html)

Or they occur either in a global position, i.e. they are created while launching the server:

  let v = {Js.string{ Js.string "a client side string" }}

All global client values are sent with the initial request of a client process. They are evaluated during the initialization of the client program, i.e. before setting up the document. This is necessary in order to safely inject them into the next client-section.

Thus, if you want to refer the DOM in global client values (e.g. by injecting variables holding Html5-elements) you must be sure to delay this by using Error a_api: exception Api.Error("invalid contents: Eliom_client.onload").

Request client values, however, are sent with the next response to the client and evaluated after setting up the (possibly) sent document.

Sending client values

There are several ways how a server side client value can be sent to the client, accessing its concrete value. Firstly, the most basic one is by just injecting it to another client value:

  let _ = My_app.register_service ~path ~get_params
    (fun _ _ ->
       let v = {int{ 42 }} in (* v is abstract *)
       ignore {unit{
         Eliom_lib.alert "It's %i!" %v (* %v is 42 here ! *)
       Lwt.return html

In the above example, first a client value v (with the abstract type int client_value) is declared. Then a second client of type unit value is created. The injection %v has then the concrete value 42.

Here, the second client value is ignored; however, it will be evaluated on client side, executing the side effect, just because it has been created. A very neat way to do client side programming inside the service handler!

Secondly, client values of type (Dom_html.event Js.t -> unit) client_value can be used as event handler in the construction of Eliom Html5 elements:

  let service_handler get post =
    let onclick = {{ fun ev -> Eliom_lib.alert "Thanks." }} in
    let div = div ~a:[a_onclick onclick] [pcdata "Click me!"] in
    Lwt.return (html head [div])

Note here, how the type annotation of a client value can be omitted if the type of the client value is inferable from its usage in the server code (as the argument to on_click in the example).

Thirdly, global client values can also be injected into the client section.

Restrictions to injections into the client values/sections

It is not possible to send values containing closures that way. There are two executables: one on server side, and one one on client side, and closures just contains a pointer to the code. Extra code is never sent dynamically on the network. This means that unforced lazy values, objects, or anything containing functions can't be send. Functions must be defined on the side(s) where they will run. Most of the time, you can do all that you want by using client-values or server-functions. Some eliom types use a specific mechanism to circumvent this limitation. This is the case of: services, comet channels and buses. To use this mechanism see chapter Wrapping values.

Those values are typechecked "by name": the most general type of a variable is inferred for server side then use as a type constraint on client side. For instance

let value = (... : t) in
let v = {typ{ ... %value ... }}

can be read as

let value = (... : t) in
let v = {typ{ ... (%value: t) ... }}

As client and server code are compiled separately, this means that a code like the following would be incorrect but would typecheck.

type a = A of int
{client{ type a = A of string }}
let value = A 1 in
let v = {{ match %value with A s -> Dom_html.window##alert(Js.string s) }}

Note that for some reason, it is impossible to use the {...{ }} and {{ }} syntax inside a module. For {{ }} you can usually circumvent this limitation by declaring a function at toplevel with all the %variable as parameters.

Technical Documentation: Implementation of the language extensions

Do not read this section if you do not feel the urgency to dive deeply into the implementation details of Eliom, or if you have a weak stomach!

This sections gives some details on the implementation of the language extensions in Eliom. To investigate this in more details, it is advisable to dump the source code for the server or client program with with eliomc -c -infer or js_of_eliom -c -infer respectively, and to refer to the API of the generated functions in Eliom_service.Syntax_helpers on the server and Eliom_client.Syntax_helpers on the client.

Client values

For each occurrence of a client value {typ{ exp }} with injected variables v_1, ..., v_n in the source code, i.e. static occurrence, at a location p, the syntax extension registers with p as the closure ID a function

fun (v'_1, ..., v'_n) -> exp'

where exp' is exp with occurrences of %v_i replaced by v'_i where all v'_i are free in exp.

When a client value is then created dynamically, a Eliom_lib_base.client_value_datum is registered to be sent to the client with the Eliom_lib_base.global_data or Eliom_lib_base.request_data respectively. It contains the closure ID, an instance ID (unique per client value), and the tuple of injected values (sometimes known as args).

On the client side, that client value datum will be used to register an actual client value: A client closure is obtained by the specific closure ID, and it is applied on the tuple of injected values. Finally, the result is registered in a client value table for that specific closure ID and instance ID.

The representation of a client value on the server side is comprised just of the respective closure ID, and the instance ID. When it reaches the client, it is unwrapped to the concrete value by looking it up in the client value table mentioned before.


The usage of an injection registers the value under a specific identifier on the server side. All injections are sent to the client with the initial request as a table mapping those identifiers to (untyped) values.

On the client side, at the beginning of each client/shared section, all novel injections are registered in a global table of injections. This must be done post-hoc because client values are unwrapped late.

The syntax extensions then generates for an injection just a lookup in that global table for its identifier. Type constraints are generated from the syntax extension.