odoc for authors

This manual describes the features that are available and recommended for users to write great documentation using odoc. See also the page listing the language features odoc understands.

  1. Getting started
  2. Documenting your interfaces
  3. Writing documentation pages

Getting started

To generate documentation for your project, you will almost always be using odoc indirectly rather than executing it yourself. There are currently several 'drivers' of odoc at time of writing, each with their own strengths and weaknesses:

Documenting your interfaces

Odoc is built to produce documentation for your libraries, and the unit of organisation is the module. Documentation is written by putting special comments into the source of the module interface or less commonly your module implementation.

For the HTML output, odoc will produce one page for each module, module type, class and class type. This includes any submodules, so if a module A contains module B then two HTML pages will be created, and A/index.html will have a link to A/B/index.html at the point that module B is defined. So each module, module type, class and class type should be documented keeping in mind that it will end up as a single page with a table of contents.

For each module, odoc will describe all of the values, types, module types, classes, modules and all other elements found in module signatures, documenting the details relevant to the OCaml type system along with any documentation written in specially formatted comments in the source code. For any element that references any other, for example a value that has a type, odoc will create links such that clicking them will link to the definitions.

The job of the library author is therefore to organise the module interface in a logical manner and write comments explaining each type, value, exception, type constructor and everything else. The comments are written in a rich markup language that allows the usual formatting constructs: bold, italic, sub- and super-script, lists, verbatim and code sections, along with section headings, a very rich cross-referencing mechanism and tags to add specific information to individual elements.

All of the OCamldoc documentation comments described in the OCaml manual are supported, with a few important differences. In addition, it is intended that all features of the OCaml language are supported by odoc. For examples of how the language features are handled, see the Features page.

Comment placement

Comments containing documentation are known as special comments. They are like normal comments except they have precisely two asterisks at the start:

(* Normal comment *)
(** Documentation comment *)
(*** Normal comment *)

From here on in 'comment' will refer to the special comments. Most comments will be associated with particular elements, and this requires the comment to be immediately before or after an element with no blank lines in between (although non-special comments are allowed). Comments that are not associated with a particular element are known as 'floating' comments.

If there is ambiguity, which can happen if there are two elements with a comment directly in between, the comment will be associated with both elements. This is an example of where odoc differs from OCamldoc - read more about that on the OCamldoc Differences page.

type x
(** Ambiguous comment, associated with {e both} {!x} and {!y} *)
type y

This behaviour, while inherited from the compiler, is unlikely to be desired so a blank line should be inserted to make it clear to which element the comment should be associated. Note that Dune will raise an error if there are ambiguous comments in the source files.

The first comment of a module is special - it is associated with the module as a whole. This is discussed in more detail in the section on page structure.

The OCaml manual has a helpful example of comment placement, reproduced here. Note that there is an additional line inserted to avoid an ambiguous special comment.

(** The first special comment of the file is the comment associated
  with the whole module.*)

(** Special comments can be placed between elements and are kept
  by the OCamldoc tool, but are not associated to any element.
  [@]-tags in these comments are ignored.*)

(** Comments like the one above, with more than two asterisks,
  are ignored. *)

(** The comment for function f. *)
val f : int -> int -> int
(** The continuation of the comment for function f. *)

(* Hello, I'm a simple comment :-) *)
exception My_exception of (int -> int) * int
(** Comment for exception My_exception, even with a simple comment
  between the special comment and the exception.*)

(** Comment for type weather  *)
type weather =
  | Rain of int  (** The comment for constructor Rain *)
  | Sun  (** The comment for constructor Sun *)

(** Comment for type weather2  *)
type weather2 =
  | Rain of int  (** The comment for constructor Rain *)
  | Sun  (** The comment for constructor Sun *)
(** I can continue the comment for type weather2 here
because there is already a comment associated to the last constructor.*)

(** The comment for type my_record *)
type my_record = {
  foo : int;  (** Comment for field foo *)
  bar : string;  (** Comment for field bar *)
(** Continuation of comment for type my_record *)

(** Comment for foo *)
val foo : string
(** This comment is associated to foo and not to bar. *)

val bar : string
(** This comment is associated to bar. *)

class cl :

    (** Interesting information about cl *)

(** The comment for class my_class *)
class my_class :
    inherit cl
    (** A comment to describe inheritance from cl *)

    val mutable tutu : string
    (** The comment for attribute tutu *)

    val toto : int
    (** The comment for attribute toto. *)

    (** This comment is not attached to titi since
      there is a blank line before titi, but is kept
      as a comment in the class. *)

    val titi : string

    method toto : string
    (** Comment for method toto *)

    method m : float -> int
    (** Comment for method m *)

(** The comment for the class type my_class_type *)
class type my_class_type =
    val mutable x : int
    (** The comment for variable x. *)

    method m : int -> int
    (** The comment for method m. *)

(** The comment for module Foo *)
module Foo : sig
  val x : int
  (** The comment for x *)

  (** A special comment that is kept but not associated to any element *)

(** The comment for module type my_module_type. *)
module type my_module_type = sig
  val x : int
  (** The comment for value x. *)

  (** The comment for module M. *)
  module M : sig
    val y : int
    (** The comment for value y. *)

    (* ... *)

The result of odoc documenting this interface can be seen on the examples page here.

There are no differences in how odoc handles comment placement between .ml and .mli files, which is another difference from OCamldoc.

Basic markup

Text within the comments can be formatted using the following markup. Firstly, the simple typesetting markup:


Unordered lists:

{ul {- item}
    {- item}
    {- item}}

and ordered lists:

{ol {- item 1}
    {- item 2}
    {- item 3}}

There is also an abbreviated syntax for lists. The above could be written:

- item
- item
- item


+ item 1
+ item 2
+ item 3

For inline source code style, use square brackets: [ ... ] . For longer, preformatted sections of code, use the enclosing tags {[ .. ]} . For verbatim (non-source) formatted sections, use the enclosing tags {v ... v}.


In most contexts, the characters { [ ] } @ all need to be escaped with a backslash. In inline source code style, only square brackets need to be escaped. However, as a convenience, matched square brackets need not be escaped to aid in typesetting code. For example, the following would be acceptable in a documentation comment:

The list [ [1;2;3] ] needs no escaping

In a code block section, the section is ended with a ]} , and in a verbatim formatted section, the section is ended with a whitespace character followed by v} . It is not currently possible to escape this in either case.

A link to a URL may be put into the text as follows:

(** See {{: https://www.ocaml.org/ }the OCaml website} for news about OCaml *)

This will render as a link to https://www.ocaml.org/ with the text "the OCaml website"

References are links to other elements - e.g., comments might wish to refer to a module or type elsewhere as follows:

(** See the module {!Stdlib.Buffer} for more details *)

While odoc supports the syntax for references used by OCamldoc, it has an improved syntax that allows for disambiguating in the face of clashing names. See the section reference_resolution for an example of this.

The supported methods for referring to elements are:

The prefixes supported are:

In some cases the element being referenced might have a hyphen or a dot in the name, e.g. if trying to refer to a page from a .mld file "1.2.3.mld". In this case, the element name should be quoted with double quote marks:

Module lists

odoc supports a special reference type for referring to a list of modules. The markup is:

{!modules: A B C}

This will generate a list of links to these modules. If the module has a synopsis (see later), this will be inserted into the list.

Reference Scope

odoc uses the same scoping as OCaml when resolving references, but with one major difference. In a particular signature, all elements are in scope, even those later in the signature. Consider the following example:

(** In this floating comment I can refer to type {!t} and value {!v}
    declared later in the signature *)

type t

val v : t

Elements from parent modules are also in scope in child modules. Therefore the following will also work:

val x : int
val y : int

module A : sig
  (** In this module I can refer to val {!x} declared above as well as
      type {!u} declared later in the parent module. Elements declared
      in this signature take priority, so {!y} refers to {!A.y} as
      opposed to the [y] declared in the parent signature.  *)

  val y : string

type u

The above example can be seen rendered in the module Odoc_examples.Scope.

odoc allows modules to be 'opened' for the purposes of resolving references. By default, the module Stdlib is 'opened', allowing references like {!List.t} to work. This feature is enabled via the command-line flag '--open'. Currently inline open statements do not bring other elements into scope.

In order for odoc to resolve links to other compilation units or .mld pages, the referenced unit or page must be compiled and available to odoc. That is, when performing the odoc link command, one of the include paths passed via the command-line argument -I must contain the relevant .odoc file. This is normally the responsibility of the driver.


Tags are used to provide specific information for individual elements, such as author, version, parameters, etc. Tags start with an @ symbol, appear at the end of documentation comments, and are not allowed elsewhere. They should appear on their own lines, with nothing but whitespace before them.

There are three types of tag: those with no associated data (simple tags), those with a single line of text (line tags) and those with a block of marked-up text (block tags).

Simple Tags

The three tags with no data are hints to the HTML renderer to do with includes. These are:

Line Tags

These tags have a single line of data associated with them, string in the examples below. They are:

The content of the tag is then the rest of the line, and it is uninterpreted, i.e., there shouldn't be any odoc markup.

Block Tags

These tags have a block of potentially marked-up text associated with them, and occasionally some more data too. The block of text is ended by the end of the comment or by another tag. They are:

Stop Comments

The special comment:


is a stop comment. It acts as a toggle, causing subsequent elements to be omitted from the documentation. If the stop comment is repeated the subsequent items will be visible once more.

The OCaml manual provides an instructive example:

class type foo =
    (** comment for method m *)
    method m : string


    (** This method won't appear in the documentation *)
    method bar : int

(** This value appears in the documentation, since the Stop special comment
    in the class does not affect the parent module of the class.*)
val foo : string

(** The value bar does not appear in the documentation.*)
val bar : string

(** The type t appears since in the documentation since the previous stop comment
toggled off the "no documentation mode". *)
type t = string

The output of this as rendered by odoc is here.

Page Structure

Producing good documentation for your library is more than simply annotating the various modules, type and functions that are contained however. odoc expects the documentation to be structured in a logical way, and will work best if the following conventions are applied.

The overall structure is that modules start with a preamble or 'Lead Section' that serves as an overview of the most important information about the module. This is followed by the content of the module, organised into sections and subsections, the structure of which will be used to populate a table of contents which will be placed in the HTML immediately after the preamble, and rendered by default as a sidebar.

The first paragraph of the preamble will be treated as the module synopsis, and will be used as a short description of the module when it appears in a list of modules elsewhere in the documentation of the library.


The top-comment is the first element of a signature, if it is a documentation comment. For example, in an .mli file:

(** This is the top-comment of the current module. *)

module M : sig
  (** This is the top-comment of [M]. *)

    (* ... *)

As an exception, open statements are allowed to be placed before the top-comment. For example:

(* Copyright header *)

open Base

(** This is the top-comment *)

(* ... *)

Note that the top-comment can't be attached to a declaration, for example:

(** This is {e not} the top-comment because it's attached to [t]. *)
type t


The preamble is composed of the comment attached to a declaration and the top-comment of the corresponding signature, if there is one. It is special only because it will be placed in the header part of the page, just before the table of contents (if any), and is used to compute the synopsis.

(** This is the comment attached to the declaration. This paragraph will be the
    first of the preamble. *)
module M : sig
  (** This is the top-comment of the expansion. This paragraph will be the
      second of the preamble. *)

  (* ... *)

The preamble stops at the first heading, the rest is moved into the content part of the page. For example, the next two snippets will render the same way:

module M : sig
  (** Preamble.

      {1 Heading}

      This paragraph is not part of the preamble. *)
module M : sig
  (** Preamble. *)

  (** {1 Heading}

      This paragraph is not part of the preamble. *)

Note: A comment attached to a declaration shouldn't contain any heading.


The synopsis of a module (a module type, a class, etc..) is the first paragraph of the Preamble, if the preamble starts with a paragraph.

It is rendered in {!modules:...} lists and after expanded aliases.

Note that the synopsis is computed on top of the preamble, in these two examples, the synopsis is the same:

(** This paragraph is the synopsis of the module [M].

    This paragraph is no longer the synopsis and won't be rendered in the
    current page near the declaration of [M]. This paragraph will be part of
    [M]'s preamble. *)
module M : sig
  (* ... *)
module M : sig
  (** This paragraph is the synopsis of the module [M]. *)

  (* ... *)

Sections and Headings

Both API references and documentation pages can be split into sections that can be introduced with level-1 headings. Each section can also have subsections (level-2) and subsubsections (level-3).

Additionally paragraphs can be annotated with level-4 or level-5 headings. Note that paragraph headings are not be included in the generated table of contents and thus should be used to introduce examples, comments or other complementary notes. An alternative would be to consider splitting into multiple files.

The syntax for declaring sections is as follows:

{[0-6] text}


{[0-6]:label text}

where the number represents the sectioning level. 0 is reserved for page titles for .mld files. label is an optional label for the section, allowing it to be referenced via the {!label-...} reference. For example:

{2:foobar Foo Bar}
See {!label-foobar} for details

In this case the reference text would be "Foo Bar" so the paragraph would read "See Foo Bar for details".

Writing Documentation Pages

Files with the .mld extension are called documentation pages and should be used to complement API references with tutorials or guides. They are particularly suitable for OCaml and Reason because cross-references to definitions, both in the current package and for external packages, are supported.


The format of these .mld files is simply text that should be marked-up with the usual odoc markup as described in this page. A documentation page can be seen as a single regular docstring in a separate file.

Page Title

When defining a documentation page make sure to supply a page title as one is not generated by default (unlike for API reference documents where the module or module type name is used). The level-0 heading must be used for that purpose. For example:

{0 My page}

Only one title is allowed per page, the following heading levels should be in the range from 1 to 5 (inclusive). Don't worry, odoc will generate a warning if you forget accidentally include multiple titles.


The recommended way to setup documentation pages for your project is by using the Dune build system. It will automatically find and generate HTML for all mld files in your project. See Dune's configuration instructions for more details.

Referencing Pages

Currently the generated HTML pages are not be automatically referenced in the index page, you must manually add links to point to the pages in your document.

For example, if you have a page called my_page.mld, you can create a link to it with {{!page-my_page}My page} in your index.mld or anywhere else in your documentation.