Library API

Laika's API is fully referentially transparent, does not throw exceptions and avoids any kind of runtime reflection. Its core module is also available for Scala.js. The library only has a very limited number of dependencies and does not require the installation of any external tools.

This chapter only covers the topics which are specific to using Laika without its sbt plugin, while most other parts of the manual apply to both use cases, library and plugin.


Which of the modules you need to add to the build depends on the functionality you intend to use.

If you want to stick to pure transformations from string to string and don't need file or stream IO or any of the binary output formats like EPUB or PDF, you are fine with just using the laika-core module:

libraryDependencies += "org.planet42" %% "laika-core" % "0.17.1" 

This module is also 100% supported for Scala.js, so you can alternatively use the triple %%% syntax if you want to cross-build for Scala.js and the JVM:

libraryDependencies += "org.planet42" %%% "laika-core" % "0.17.1" 

If you want to add support for file and stream IO and/or output in the EPUB format, you need to depend on the laika-io module instead:

libraryDependencies += "org.planet42" %% "laika-io" % "0.17.1" 

This depends on laika-core in turn, so you always only need to add one module as a dependency and will get the rest via transitive dependencies. No module apart from laika-core is available for Scala.js, so you are in JVM land here.

Finally PDF support comes with its own module as it adds a whole range of additional dependencies:

libraryDependencies += "org.planet42" %% "laika-pdf" % "0.17.1" 

Again, this builds on top of the other modules, so adding just this one dependency is sufficient.

Anatomy of the API

The library API for creating parsers, renderers or transformers, while aiming to be intuitive and simple, also exposes one of the main design goals of Laika: that everything is meant to be pluggable.

The following example for creating a pure transformer shows the main building blocks:

Anatomy of the API

If you require support for file/stream IO, templating or binary output formats, the laika-io module expands on the core API to add this functionality:

Anatomy of the IO API

The blue elements of the API are identical to the pure transformer. The new io method used above becomes available with import

This API introduces a dependency on cats-effect which is used to model the effectful computations. You can use it with any effect that supports the cats-effect type classes, like cats-IO, Monix or Zio.

Using a Transformer is the most convenient option when you want to go directly from the raw input format (text markup) to the final output format. There are also Parser and Renderer instances that only do one half of the transformation.

All types, Transformer, Parser and Renderer come in two flavors each. A simple, pure API for dealing with strings as input and output and a parallel, effectful API for processing entire directories with templates and configuration files in addition to the usual text markup input.

Transforming Strings

Transforming in-memory input is the simplest form of transformation, and works for Markdown and reStructuredText as input, and for HTML as output. EPUB and PDF both require additional modules and are described later in this chapter.

For most cases where you don't use any of the customization hooks, you should be fine with just these imports:

import laika.api._
import laika.format._

As the next step you can then create transformer instances, which in most cases you'll want to reuse with different inputs to reduce the memory footprint and initialization overhead. The instances are immutable and thread-safe.

Example for creating a transformer for Markdown, including the GitHub-Flavor extensions:

val transformer = Transformer

Example for creating a transformer for reStructuredText:

val transformer = Transformer

In addition to specifying markup and output formats and extensions, there are many other customization options in the builder step immediately before calling build. They are identical for the pure and IO parser and are summarized in the Configuration section.

Finally you can use the transformer with text markup as input:

val result = transformer.transform("hello *there*")

The result is of type Either[ParserError, String].

Setup for Effectful Transformations

In case you want to go beyond pure string transformations, you need to switch your dependency to the laika-io module as shown in the Dependencies section.

This dependency will add file/stream IO, theme support, templating and the ability to process entire directories.

This module depends on cats-effect, and models all side effects in an abstract effect, so that you can use it with cats IO, Monix or Zio.

With the dependency in place you also need to add a third import to those you used for pure transformations:

import laika.api._
import laika.format._

The remainder of the setup depends on whether you are Using cats.IO, Monix or Zio or whether you build Applications without Effect Library.

Using cats.IO, Monix or Zio

When your application is already using one of the effect libraries compatible with the cats-effect type class hierarchy, integrating Laika will be seamless.

The exact mechanics of your Laika transformer setup will depend on which of the aforementioned libraries you are using and whether you keep all of you code in an abstract F[_] instead of coding against a concrete effect type.

The following example assumes the use case of an application written around abstract effect types and using IOApp from cats.IO for initialization:

def createTransformer[F[_]: Async: ContextShift]
    (blocker: Blocker): Resource[F, TreeTransformer[F]] =

The setup method above can then be used inside IOApp initialization logic:

object MyApp extends IOApp {

  def run(args: List[String]) = {
    Blocker[IO].use { blocker =>
      val transformer = createTransformer[IO](blocker)
      // other setup code

This way Laika gives full control over the ExecutionContext in which the blocking IO and CPU-bound operations are performed.

Setup for other libraries would be similar, Monix for example comes with a TaskApp which is similar to IOApp.

Applications without Effect Library

When using other stacks like Akka HTTP or the Play framework, you need to bridge the gap between the worlds of cats-effect and the surrounding toolkit, often centering around the Future API (which, in contrast to cats-effect, is not referentially transparent).

First, to create a transformer with the setup method created in the previous example, you need to create instances of ContextShift and Blocker yourself:

implicit val cs: ContextShift[IO] = 
val blocker = Blocker.liftExecutionContext(

val transformer = createTransformer(blocker)

The resulting instance can then be used to describe a transformation:

val result: IO[String] = transformer.use {

The resulting IO instance is only a description of a program, as it starts evaluation lazily, in contrast to the Scala SDK's Future which starts processing eagerly.

For actually running the effect and producing a result, you have several options depending on the stack you are using. One common scenario is a toolkit like Akka HTTP or Play where you execute a route that is expected to return a Future. This can achieved by a simple translation:

val futureResult: Future[String] = result.unsafeToFuture()

Other options not involving Future are either blocking, synchronous execution:

val syncResult: String = result.unsafeRunSync()

Or asynchronous execution based on a callback:

result.unsafeRunAsync {
  case Left(throwable) => handleError(throwable)
  case Right(result)   => handleResult(result)

Do not get too hung up on the scary sound of all these unsync... methods. The one kind of safety you are loosing when using them is referential transparency. But if you are using a Future-based API for example, your program is not referentially transparent anyway.

Entire Directories as Input

The effectful transformer is the most powerful variant and also the one that is the basis for the sbt plugin. It expands the functionality beyond just processing markup files to also parsing templates and configuration files as well as copying static files over to the target directory.

val transformer = Transformer

The above transformer can then be used to process a directory of markup, template and configuration files:

val res: IO[RenderedTreeRoot[IO]] = transformer.use {

The target directory is expected to exist, while any required subdirectories will be automatically created during rendering. There will be one HTML file for each input file in the same directory layout.

If you replace the HTML renderer in the example above with one of the binary formats (EPUB or PDF), the API will be slightly different, as the toDirectory option is replaced by toFile. This is because these formats always produce a single binary result, merging all content from the input directories into a single, linearized e-book:

val res: IO[Unit] = transformer.use {

Merging Multiple Directories

All previous examples read content from the single input directory. But you can also merge the contents of multiple directories:

val res: IO[RenderedTreeRoot[IO]] = transformer.use {
  _.fromDirectories("markup", "theme")

This adds some additional flexibility, as it allows, for example, to keep reusable styles and templates separately. This flexibility is possible as Laika is not tied to the file system, but instead provides a Virtual Tree Abstraction.

Freely Composing Inputs

If you need even more flexibility instead of just configuring one or more input directories, e.g. when there is a need to generate content on-the-fly before starting the transformation, you can use the InputTree builder.

val inputs = InputTree[F]
  .addDirectory("/path-to-my/images", Root / "images")
  .addClasspathResource("templates/default.html", DefaultTemplatePath.forHTML)
  .addString(generateStyles(), Root / "css" / "site.css")

In the example above we specify two directories, a classpath resource and a string containing CSS generated on the fly. By default directories get merged into a single virtual root, but in the example we declare a mount point for the second directory, which causes the content of that directory to be assigned the corresponding logical path.

Always keep in mind that declaring inputs and outputs are the only places in the Laika universe where you'd ever use concrete file system paths. Beyond this configuration step you are entirely within Laika's virtual path abstraction and refer to other resources by their virtual path. This is true for linking, using image resources, specifying templates to use in configuration headers, and so on. It means that everything you can refer to in your markup files needs to be included in the input composition step.

The InputTreeBuilder API gives you the following options:

When generating input on the fly it is usually a question of convenience or reducing boilerplate whether you choose to generate a string for parsing or the AST directly.

For the complete API see InputTreeBuilder.

The customized input tree can then be passed to the transformer:

val res: IO[RenderedTreeRoot[IO]] = transformer.use {

Preparing Content

Laika does not have any special directories and content can be nested in sub-directories down to arbitrary levels. For more details on how to organize content, see Directory Structure.

Transformers and Parsers distinguish between the following file types:

Separate Parsing and Rendering

So far all examples in this chapter showed the use of a transformer instance, which takes you all the way from a raw input file to the target format, e.g. from Markdown to HTML.

But in some cases you may just want to execute one half of the process:

In fact if you examine the implementations of the various pure and effectful transformer types, you'll notice that it is mostly trivial: delegating to the underlying Parser and Renderer and just piping the result from one to the other.

The following code example demonstrates the third scenario listed above: Rendering the same input to multiple output formats.

First we create a parser that reads from a directory:

val parserRes = MarkupParser

Next we create the renderers for the three output formats:

val htmlRendererRes = Renderer.of(HTML).io(blocker).parallel[IO].build
val epubRendererRes = Renderer.of(EPUB).io(blocker).parallel[IO].build
val pdfRendererRes  = Renderer.of(PDF).io(blocker).parallel[IO].build

Since all four processors are a cats-effect Resource, we combine them into one:

val allResources = for {
  parser <- parserRes
  html <- htmlRendererRes
  epub <- epubRendererRes
  pdf <- pdfRendererRes
} yield (parser, html, epub, pdf)

Finally we define the actual transformation by wiring the parser and the three renderers:

val transformOp: IO[Unit] = allResources.use { 
  case (parser, htmlRenderer, epubRenderer, pdfRenderer) =>
    parser.fromDirectory("source").parse.flatMap { tree =>
      val htmlOp = htmlRenderer.from(tree.root).toDirectory("target").render
      val epubOp = epubRenderer.from(tree.root).toFile("out.epub").render
      val pdfOp = pdfRenderer.from(tree.root).toFile("out.pdf").render
      (htmlOp, epubOp, pdfOp).parMapN { (_, _, _) => () }

We are using cats' parMapN here to run the three renderers in parallel.

The tree instance passed to all renderers is of type DocumentTreeRoot. If necessary you can use its API to inspect or transform the tree before rendering. See The Document AST for details.

The sample code in this scenario showed the effectful variant of the Parser and Renderer types, but just as the Transformer they exist in the other flavor as well: a pure variant as part of the laika-core module.


All the examples in this chapter only scratched the surface of Laika's API, focusing on the basics like specifying input and output. But since Laika is designed to be fully customizable and serve as a toolkit for creating toolkits, there is whole range of options available for customizing and extending its built-in functionality.

Most of these configuration options are not specific to the library API use case and apply to the sbt plugin as well, apart from differences in the syntax/mechanics which with they are applied, which are reflected in the corresponding code examples. For this reason this section only gives a very brief overview while linking to the relevant sections in the other chapters.

Theme Configuration

Configuration for the built-in Helium theme or a 3rd-party theme can be passed to the transformer builders. You can also specify an empty theme if you want to put all templates and styles right into your input directory.

Example for applying a few Helium settings:

val theme = Helium.defaults
    title = Some("Project Name"),
    language = Some("de"),
val transformer = Transformer

Setting an empty theme:

laikaTheme := Theme.empty

If you do not explicitly pass a theme configuration Laika will run with the default settings of the Helium theme, meaning your site and e-books will look exactly like this documentation, including all color and font choices.

Since the default Helium theme offers a lot of configuration options it has its own dedicated chapter Theme Settings. They are specific to the Helium theme, when using a 3rd-party theme you need to consult their documentation for setup options.

To just give a brief overview, those settings allow you to configure:

Other Settings

These other settings are available independent from the theme in use: