Observable Notebook Kit

Observable Notebook Kit is an open-source command-line tool for building static sites from Observable Notebooks based on an open file format. Notebook Kit also includes a Vite plugin and a low-level JavaScript interface for deep integration of Observable Notebooks with custom web applications.

Notebook Kit is available as part of Notebooks 2.0 Technology Preview, which includes Observable Desktop, a macOS application for editing notebooks.

For more on authoring notebooks, see the Notebooks system guide.


Installing

You can install Notebook Kit from npm:

npm add @observablehq/notebook-kit

If you use pnpm, you must also explicitly install any fonts used by the default styles (unless you plan on using different fonts):

pnpm add @observablehq/notebook-kit @fontsource/inter @fontsource/source-serif-4 @fontsource/spline-sans-mono

If you plan on using database connectors, you must also install the drivers for the databases you wish to use. To install the DuckDB driver:

npm add @duckdb/node-api

To install the Postgres driver:

npm add postgres

To install the Snowflake driver:

npm add snowflake-sdk

To install the Google BigQuery driver:

npm add @google-cloud/bigquery

To install the Databricks driver:

npm add @databricks/sql

Notebook Kit requires Node.js 20.19+ or 22.12+. To use the SQLite database connector, you must use Node.js 24+. Bun 1.2+ should also work, but is not officially supported.


The notebook file format

The notebook file format is intended to be simple, human-readable, and human-editable. It’s based on HTML, which means you get nice editing affordances in today’s text editors without needing special plugins. In addition, it’s easy to review diffs when storing notebooks in source control, to search, to find-and-replace, and countless other workflows.

A notebook HTML file consists of:

  • a <notebook> root element,
  • an optional <title> element, and
  • one <script> element per cell.

Here’s a simple “hello world” notebook to demonstrate:

<!doctype html>
<notebook>
  <title>Hello, world!</title>
  <script type="text/markdown">
    # Hello, world!
  </script>
  <script type="module" pinned>
    1 + 2
  </script>
</notebook>

The cell <script> element should use four spaces of indentation for each line; these four leading spaces are trimmed when parsing the notebook. (In the future, we’ll likely make this contextual to allow varied indentation.)

The <script> element supports the following attributes:

  • type - the cell type (also known as the cell language or cell mode)
  • pinned - if present, show the cell’s source code
  • hidden - if present, suppress the cell’s implicit display
  • output - for non-JavaScript cells, how to expose the value
  • database - for SQL cells, which database to query
  • id - the cell identifier, for stable editing (optional)

The type attribute can have one of the following values:

  • module - JavaScript
  • text/markdown - Markdown
  • text/html - HTML
  • application/sql - SQL
  • application/x-tex -
  • text/vnd.graphviz - DOT (Graphviz)
  • application/vnd.observable.javascript - Observable JavaScript

Here’s an example of each supported cell type.

A JavaScript (module) cell:

<script type="module">
  1 + 2
</script>

A Markdown (text/markdown) cell:

<script type="text/markdown">
  # Hello, world!
</script>

An HTML (text/html) cell:

<script type="text/html">
  <h1>Hello, <i>world</i>!</h1>
</script>

A SQL (application/sql) cell:

<script type="application/sql" database="reporting" output="customers">
  SELECT * FROM customers
</script>

A (application/x-tex) cell:

<script type="application/x-tex">
  \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
</script>

A DOT (text/vnd.graphviz) cell:

<script type="text/vnd.graphviz">
  digraph G {
    A -> B -> C;
    A -> C;
  }
</script>

An Observable JavaScript (application/vnd.observable.javascript) cell:

<script type="application/vnd.observable.javascript">
  foo = 42
</script>

The pinned attribute determines whether to show the cell’s source code. The hidden attribute suppresses the cell’s implicit display; for example, you can use it hide a SQL cell’s results table. Cells can also have an id attribute to indicate a unique (positive integer) identifier; an id isn’t required, but gives cells a stable identity for editing.

Since each cell is specified as a <script> element, if the cell source code contains a </script>, it must be escaped with a backslash as <\/script>. Likewise, a sequence of backslashes in this context must be escaped with an additional backslash, such as <\\/script>.

The <notebook> element supports the following attributes:

  • theme - the theme
  • readonly - if present, disallow editing

The theme attribute may have one of the following values:

  • air (default)
  • coffee
  • cotton
  • deep-space
  • glacier
  • ink
  • midnight
  • near-midnight
  • ocean-floor
  • parchment
  • slate
  • stark
  • sun-faded

See Observable Framework for theme previews. More themes may be added in the future. Built-in themes can be augmented using standard stylesheets.

See notebook.ts for TypeScript types representing notebooks and cells.


The page template format

Notebook Kit supports custom page templates when previewing and building notebooks. Page templates can provide additional chrome around the notebook, such as a header and footer. A page template HTML file can contain whatever you like. Notebook Kit will insert or modify the following elements:

  • <meta name="generator"> - Notebook Kit version and attribution
  • <title> - a title suffix, appearing after the notebook title
  • <main> - where to render the notebook cells

The default template looks something like this:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style type="text/css">
      @import url("observable:styles/index.css");
    </style>
  </head>
  <body>
    <main></main>
  </body>
</html>

Command-line interface

Notebook Kit’s CLI supports four commands: preview for a live preview of notebooks, build for building a static site, download for downloading Observable Notebooks as HTML, and query for saving the results of database queries.

We recommend that you install Notebook Kit locally to a project using a package manager such as npm, and then add preview and build scripts to your package.json.

{
  "dependencies": {
    "@observablehq/notebook-kit": "^1.2.0"
  },
  "scripts": {
    "docs:preview": "notebooks preview --root docs",
    "docs:build": "notebooks build --root docs -- docs/*.html"
  }
}

Preview

To start the preview server, run the preview command. Use the --root argument to tell the preview server where the notebooks are located.

notebooks preview --root docs

If you’d like to use a custom page template, use the --template argument:

notebooks preview --root docs --template docs/custom.tmpl

Build

To convert notebooks into a static site, run the build command. Use the --root argument to tell the command where the notebooks are located; you must also provide a list of notebook HTML files as positional arguments. By default, the site will be built in .observablehq/dist within the root.

notebooks build --root docs -- docs/*.html

Or, with a custom template:

notebooks build --root docs --template docs/custom.tmpl -- docs/*.html

Download

To download and convert an existing Observable Notebook to a local HTML file, run the download command.

notebooks download https://observablehq.com/@d3/bar-chart > docs/bar-chart.html

Query

Run the query command to save the result of a database query. (This is typically done automatically, but can be used to prepare or refresh queries manually.) Pass positional arguments to interpolate JSON-encoded values into the query.

notebooks query --database duckdb 'SELECT 1 + ' '2'

JavaScript interface

For deeper integration of Observable Notebooks into custom web applications, we provide a Vite plugin and a low-level JavaScript interface. For more, please browse the TypeScript source, or start a discussion if you’d like help.

Vite API

The Vite plugin is recommended for Vite-based applications.

observable({template?: string, transformTemplate?: (template: string) => string | Promise<string>, transformNotebook?: (notebook: Notebook) => Notebook | Promise<Notebook>, window?: Window, parser?: DOMParser} = {}): PluginOption

Returns a Vite plugin for rendering notebook HTML as vanilla HTML. Markdown and HTML cells are rendered statically (without any interpolated dynamic values), and then, if needed, replaced with dynamic content when the page loads. Other cell modes are exclusively dynamic. Pinned cells display their source code statically rendered below any output. Supported languages are syntax-highlighted using Lezer.

config(): UserConfig

Returns the base Vite config needed to use the observable() Vite plugin; this enables top-level await, multi-page application behavior, and adds resolvers for npm:, jsr:, and observable: import protocols.

Build API

The Build API analyzes cell source code to find unbound references, such that the appropriate variable graph can be initialized using the Observable Runtime. For non-JavaScript cells (such as Markdown, HTML, and SQL), the Build API also transpiles the source into a tagged template literal expression.

maybeParseJavaScript(input: string): JavaScriptCell | undefined

Parses the specified JavaScript cell source code, returning the resulting AST as either a Program or Expression node, along with any top-level variable declarations and unbound references to other variables. If the specified input has invalid syntax, returns undefined.

parseJavaScript(input: string): JavaScriptCell

Parses the specified JavaScript cell source code, returning the resulting AST as either a Program or Expression, along with any top-level variable declarations and unbound references to other variables. (This method is called internally by transpileJavaScript.)

parseTemplate(input: string): TemplateLiteral

Parses the specified template cell source code (such as the contents of a Markdown or SQL cell), returning the resulting AST as a TemplateLiteral. (This method is called internally by transpile.)

transpile(input: Cell, options?: TranspileOptions): TranspiledJavaScript

Transpiles the specified cell. The transpiled source is returned as a body function suitable for use with variable.define from the Observable Runtime, along with any named inputs (unbound references) and outputs (top-level declarations).

transpileJavaScript(input: string, options?: TranspileOptions): TranspiledJavaScript

Transpiles the specified JavaScript cell source code. The transpiled source is returned as a body function suitable for use with variable.define from the Observable Runtime, along with any named inputs (unbound references) and outputs (top-level declarations).

transpileTemplate(input: Cell): string

Transpiles the specified template cell, such as a Markdown or SQL cell, returning the corresponding JavaScript source code consisting of a single template literal expression. (This method is called internally by transpile, and the result is passed to transpileJavaScript.)

Utility API

These utility methods define default values, allowing shorthand.

toNotebook(spec: NotebookSpec): Notebook

Given a NotebookSpec, returns the corresponding Notebook, populating any default values.

toCell(spec: CellSpec): Cell

Given a CellSpec, returns the corresponding Cell, populating any default values.

defaultPinned(mode: Cell["mode"]): boolean

Returns the default pinned value for the given cell mode.

serialize(notebook: Notebook, {document?: Document} = {}): string

Serializes the specified notebook to HTML.

deserialize(html: string, {parser?: DOMParser} = {}): Notebook

Deserializes the specified HTML to a notebook.

Runtime API

The Runtime API provides some additional logic on top of the Observable Runtime to run the notebook, allowing cells to be redefined at runtime, and implementing the notebook standard library, including the display and view functions which are scoped per-cell.

constructor NotebookRuntime(builtins?: Record<string, unknown>)

Instantiates a new notebook runtime, given the specified built-ins, which defaults to (the standard) library. The returned instance exposes a runtime, main, and define methods which are equivalent to those below, but scoped to this specific instance. This constructor allows multiple notebooks to be instantiated concurrently.

define(state: DefineState, definition: Definition, observe?: () => Observer): void

Defines a cell, given the specified state (including the root in which to display the cell’s output, and recording any secondary variables defined by the cell) and definition (including the cell’s id, body, inputs, outputs, etc.). An observe function may be specified to override the default variable observer, which is used to render the cell’s contents.

display(state: DisplayState, value: unknown): void

Displays a cell’s value (into the root given by the specified state). (This method is used internally when you call display from a cell.)

clear(state: DisplayState): void

Clears a cell’s output (from the root given by the specified state). (This method is used internally to clear the display when a cell is invalidated.)

observe(state: DisplayState, definition: Definition): void

Returns the default variable observer used to render the cell’s contents.

inspect(value: unknown, expanded?: number[][]): HTMLDivElement

Returns the rendered inspector for the given value. This is a small wrapper on top of the Observable Inspector’s inspector.fulfilled which re-expands collapsible elements when they are re-rendered.

inspectError(value: unknown): HTMLDivElement

Returns the rendered inspector for the given error value. This is a small wrapper on top of the Observable Inspector’s inspector.rejected.

getExpanded(node: Node): number[][] | undefined

Given an inspector node previously rendered by inspect(…), returns the list of paths that have been expanded by the user; passing these to a subsequent call to inspect(…) will re-expand the same set of paths after re-rendering.

root: HTMLElement

Returns the root element into which cells are rendered. This is the first <main> element, if it exists, and otherwise the <body>.

runtime: Runtime

The Observable Runtime instance.

main: Module

The main runtime module.

library: Record<string, unknown>

The built-ins provided to the runtime; the notebook standard library. This object includes the definitions for various built-ins, such as now, width, Generators, and Mutable.

Database API

The Database API provides a low-level API for running database queries.

getDatabaseConfig(sourcePath: string, databaseName: string): Promise<DatabaseConfig>

Loads the database config given a path to a notebook source file and the name of a database.

getDatabase(config: DatabaseConfig): Promise<QueryTemplateFunction>

Returns the query template function (equivalent to the sql tagged template literal) for running a query against the given database.

getQueryCachePath(sourcePath: string, databaseName: string, strings: readonly string[], …params: unknown[]): Promise<string>

Returns the path to the query results cache file, given a path to a notebook source file, the name of a database, and query template parts.

✎ Suggest changes to this page