# Configuration
`nbprint` supports three complementary ways to configure a report:
1. **Notebook-first**: point nbprint at an existing `.ipynb` and drive sectioning, styling, and page layout from cell tags, cell metadata, and a small `%%nbprint` magic.
1. **YAML-first**: describe the full document in YAML (via [`pydantic`](https://docs.pydantic.dev/latest/) models, composed with [`lerna`](https://github.com/nbprint/lerna) and [`omegaconf`](https://github.com/omry/omegaconf)).
1. **Hybrid**: start from a notebook and add YAML overlays that target cells by index, tag, section, or cell type without rewriting the notebook.
```mermaid
graph TB
subgraph Inputs
nb("notebook
(.ipynb)")
yml("YAML config
(.yaml)")
ovl("overlays
(YAML or
notebook metadata)")
end
nbct[/nbprint
template/]
pjs[/paged.js
layout engine/]
nbc{nbconvert}
o@{ shape: doc, label: "HTML / PDF" }
nb e1@-->nbc
yml e2@-->nbc
ovl -.->|merged at ingest| nbc
nbct --> nbc
pjs --- nbct
nbc e3@--> o
e1@{animate: true}
e2@{animate: true}
e3@{animate: true}
```
## Notebook-First Workflow
Point nbprint at a `.ipynb` and it is treated as a full configuration input. All notebook metadata under `notebook.metadata.nbprint` becomes configuration; cell metadata and tags drive routing, styling, and visibility.
```bash
nbprint path/to/report.ipynb
```
Worked examples live in [`examples/`](../../examples):
- [`notebook-sections.ipynb`](../../examples/notebook-sections.ipynb): section routing via tags and cell metadata, with page/output config in notebook metadata.
- [`notebook-runtime.ipynb`](../../examples/notebook-runtime.ipynb): runtime API (`NBPrintCell`) and `%%nbprint` magic.
- [`notebook-overlays.ipynb`](../../examples/notebook-overlays.ipynb): `overlays` and `layout_overlays` embedded in notebook metadata.
### Section routing from cell tags
By default every ingested cell lands in `middlematter`. To route cells to named sections, tag them:
```jsonc
// cell.metadata
{
"tags": ["nbprint:section:covermatter"]
}
```
The tag form is `nbprint:section:` where `` is any of the 13 section names (see [Structured Sections](#structured-sections) below).
### Section routing from cell metadata
Equivalent to a tag, but takes priority:
```jsonc
// cell.metadata.nbprint
{
"section": "frontmatter"
}
```
### Notebook-level page and output config
Embed a full page/output spec directly in the notebook. No YAML is required:
```jsonc
// notebook.metadata.nbprint
{
"page": {
"_target_": "nbprint.PageGlobal",
"size": "Letter",
"orientation": "portrait"
},
"outputs": {
"_target_": "nbprint.NBConvertOutputs",
"target": "pdf"
}
}
```
YAML or CLI overrides always take precedence over notebook-embedded values, so you can use a notebook's defaults and override on the command line.
### `NBPrintCell` runtime API
Inside a running notebook you can express cell-level intent through a Python object. Metadata is persisted via a hidden `display()` output with a custom MIME type; nbprint picks it up at ingestion time.
```python
from nbprint import NBPrintCell, Style, Font
NBPrintCell(section="covermatter", css=":scope { text-align: center; }")
with NBPrintCell(style=Style(font=Font(size=24))):
display(Markdown("# Cover Page"))
```
### `%%nbprint` cell magic
A lighter alternative for quick one-liners:
```python
%%nbprint section=frontmatter css=":scope { font-size: 18px; }"
display(Markdown("# Introduction"))
```
Explicit cell metadata takes precedence over runtime metadata, which takes precedence over the magic, so a hand-edited value in `cell.metadata.nbprint` always wins.
## YAML-First Workflow
YAML is the most expressive way to describe a report: every field of every pydantic model is reachable, and the lerna config system makes it easy to share pieces across reports.
A minimal example:
```yaml
---
debug: false
outputs:
_target_: nbprint.NBConvertOutputs
root: ./outputs
target: html
content:
- _target_: nbprint.ContentMarkdown
content: |
# A Generic Report
## A Subtitle
css: ":scope { text-align: center; }"
- _target_: nbprint.ContentPageBreak
- _target_: nbprint.ContentTableOfContents
- _target_: nbprint.ContentPageBreak
- _target_: nbprint.ContentFlexRowLayout
sizes: [1, 1]
content:
- _target_: nbprint.ContentFlexColumnLayout
content:
- _target_: nbprint.ContentMarkdown
content: "# Left column"
- _target_: nbprint.ContentFlexColumnLayout
content:
- _target_: nbprint.ContentMarkdown
content: "# Right column"
```
Run it:
```bash
nbprint examples/basic.yaml
```
Common content types include `ContentMarkdown`, `ContentCode`, `ContentPageBreak`, `ContentTableOfContents`, and the layout containers `ContentFlexRowLayout` / `ContentFlexColumnLayout` / `ContentInlineLayout`.
## Output formats & nbconvert configuration
The `outputs` block (`NBConvertOutputs`) controls how the generated notebook is
executed and converted. Common fields:
| Field | Default | Description |
| ---------- | --------- | --------------------------------------------------- |
| `target` | `html` | Output format: `ipynb`, `html`, `webhtml`, `webpdf` |
| `execute` | `true` | Execute the notebook before converting |
| `timeout` | `600` | Per-cell execution timeout (seconds) |
| `template` | `nbprint` | nbconvert template name |
### Passing arbitrary nbconvert / traitlets options
Anything configurable on an nbconvert exporter, preprocessor, or the app itself
is reachable through `nbconvert_config`, so nbprint doesn't need a field for each
option. Entries map 1:1 onto nbconvert's `--Class.trait=value` CLI options
and are applied to the conversion (exporter) pass.
For example, [`WebPDFExporter.page_render_timeout`](https://github.com/jupyter/nbconvert/pull/2250)
controls how long (in ms) the headless browser waits for JavaScript output to
settle before capturing the PDF. This helps with notebooks that have heavy
interactive charts:
```yaml
outputs:
_target_: nbprint.NBConvertOutputs
target: webpdf
nbconvert_config:
# flat dotted keys ...
WebPDFExporter.page_render_timeout: 5000
# ... or nested namespaces — both are equivalent
TemplateExporter:
exclude_input: true
```
The nested form maps directly onto a hydra/lerna CLI override:
```bash
nbprint examples/basic.yaml \
++outputs.target=webpdf \
++outputs.nbconvert_config.WebPDFExporter.page_render_timeout=5000
```
Scalars pass through as-is, booleans render as `True`/`False`, and lists are
JSON-encoded so container traits round-trip through the CLI.
Traits that nbprint already manages (`NbConvertApp.export_format` (`to`),
`TemplateExporter.template_name` (`template`), `NbConvertApp.output_base`
(`output`), `ExecutePreprocessor.enabled` (`execute`), and
`ExecutePreprocessor.timeout`) are rejected in `nbconvert_config` with an error
pointing at the dedicated field (`target`, `template`, `naming`/`root`,
`execute`, `timeout`). Set those via their own fields instead.
## Structured Sections
For book-style documents, `nbprint` supports 13 ordered sections grouped into 6 logical groups:
| Group | Sections | Description |
| ------------ | ---------------------------------------------------------------------- | -------------------------------------------------- |
| Prematter | `prematter` | Hidden content executed before report construction |
| Covermatter | `covermatter` | Cover page content |
| Frontmatter | `title`, `copyright`, `dedication`, `table_of_contents`, `frontmatter` | Title page, legal, dedication, TOC, preface |
| Middlematter | `middlematter`, `middlematter_separators` | Main body and chapter dividers |
| Endmatter | `appendix`, `index`, `endmatter` | Supplementary material, index, bibliography |
| Rearmatter | `rearmatter` | Back cover content |
Instead of a flat list, `content:` can be keyed by section name. Sections are rendered in document order; unused sections are simply empty.
```yaml
content:
covermatter:
- _target_: nbprint.ContentMarkdown
content: "# My Report"
css: ":scope { text-align: center; }"
table_of_contents:
- _target_: nbprint.ContentTableOfContents
middlematter:
- _target_: nbprint.ContentMarkdown
content: "# Section One"
endmatter:
- _target_: nbprint.ContentMarkdown
content: "# Bibliography"
```
Flat-list `content:` is still supported and is treated as `middlematter`.
### Chapters (list-of-lists middlematter)
For multi-chapter documents, supply `middlematter` as a **list of lists**. Each
sublist is a chapter: its **first element** is promoted to a chapter separator
and the remaining elements form the chapter body. nbprint interleaves them in
document order: `[chapter1-title, *chapter1-body, chapter2-title, *chapter2-body, …]`.
```yaml
content:
middlematter:
- # Chapter One — first item becomes the title page
- _target_: nbprint.ContentMarkdown
content: "# Chapter One"
- _target_: nbprint.ContentMarkdown
content: "Body of the first chapter…"
- # Chapter Two
- _target_: nbprint.ContentMarkdown
content: "# Chapter Two"
- _target_: nbprint.ContentMarkdown
content: "Body of the second chapter…"
```
By default each separator renders as a **standalone chapter title page**, with a hard
page break is inserted before and after it. To keep separators inline with their
chapter body instead, disable the behavior:
```yaml
content:
separator_title_pages: false
middlematter:
- [...]
```
The promoted separators are also available directly under the
`middlematter_separators` section if you prefer to populate them explicitly
alongside a flat `middlematter` list.
See `examples/chapters.yaml` for a full working example.
### Per-section page layout
Each section can carry its own page layout via `PageGlobal.pages`. This generates CSS `@page { ... }` rules and maps cells to them via `data-nbprint-section` attributes:
```yaml
page:
_target_: nbprint.PageGlobal
bottom_right:
_target_: nbprint.PageRegion
content:
_target_: nbprint.PageNumber
pages:
covermatter:
# no headers/footers on the cover
frontmatter:
counter_reset: true
counter_style: lower-roman
middlematter:
counter_reset: true
```
See `examples/sections.yaml` for a full working example.
### Section-level style defaults
Each section can carry a default `Style` that every cell in that section inherits. A cell's own `Style` fields take precedence; unset fields fall back to the section default.
```yaml
content:
section_styles:
covermatter:
font: {size: 28}
horizontal_alignment: center
frontmatter:
font: {family: "Georgia, serif"}
covermatter:
- _target_: nbprint.ContentMarkdown
content: "# My Report"
```
## Formatting Overlays
Overlays are rules that apply formatting to ingested notebook cells without modifying the notebook itself. There are three kinds.
### `Overlay` — merge formatting onto matching cells
Matches cells by index, tag, cell type, or target section, and merges `css` / `classname` / `attrs` / `style` / `ignore` into the resulting `Content`:
```yaml
overlays:
- match: {index: 0}
css: ":scope { text-align: center; }"
- match: {tag: "chart"}
style:
border: {bottom: {width: 2, style: solid, color: "#333"}}
- match: {cell_type: markdown}
classname: "prose-body"
- match: {section: covermatter}
style:
font: {size: 28}
```
Match fields combine with AND semantics, and an empty `match` matches every cell. Multiple overlays apply in list order; later overlays stack on top of earlier ones:
- `css` is appended
- `classname` is appended (accumulated as a list)
- `attrs` is merged (overlay keys win on collision)
- `style` is merged via `Style.merge` (overlay fields win)
- `ignore` is set when provided
Overlays can also come from notebook metadata. YAML-declared overlays and notebook-declared overlays compose:
```jsonc
// notebook.metadata.nbprint
{
"overlays": [
{"match": {"tag": "emphasis"}, "css": ":scope { font-style: italic; }"}
]
}
```
### `LayoutOverlay` — wrap contiguous cells in a flex container
A layout overlay matches the same way but *wraps* contiguous runs of matched cells (same section, consecutive notebook indices) in a `ContentFlexRowLayout`, `ContentFlexColumnLayout`, or `ContentInlineLayout`:
```yaml
layout_overlays:
# Any two adjacent "sidebyside"-tagged cells become a row
- match: {tag: "sidebyside"}
layout: row
sizes: [1, 1]
# Cells at notebook indices 3..5 (inclusive) get stacked in a column
- index_range: [3, 5]
layout: column
# Wrapper itself can carry CSS / classname / style
- match: {tag: "gallery"}
layout: inline
css: ":scope { gap: 8px; }"
classname: gallery-wrap
```
Non-contiguous matches produce multiple wrappers. Both kinds of overlay can be combined: formatting overlays apply to the individual cells, and a layout overlay then wraps them.
### `PageBoxOverlay` — wrap contiguous cells in a page box
Same matching rules, but the wrapper is a [`ContentPageBox`](architecture.md): the matched run becomes one logical page, arranged by a named layout preset and followed by a page break. Select it with `wrapper: page-box`:
```yaml
layout_overlays:
# Every run of "pair"-tagged cells becomes a two-column page
- match: {tag: "pair"}
wrapper: page-box
layout: columns-2
gap: 1rem
# A four-up dashboard page, in landscape
- match: {tag: "dashboard"}
wrapper: page-box
layout: grid-2x2
page_orientation: landscape
```
This is the multi-cell counterpart to [`NBPrintPage`](architecture.md), which turns a *single* cell into a page box from inside the notebook. Reach for the overlay when a page is assembled from several cells.
Available fields mirror `ContentPageBox`: `layout` (any [layout preset](architecture.md)), `fit`, `min_pages`, `gap`, `padding`, `align`, `justify`, `grid_template`, `page_size`, `page_orientation`, `page_margins`, plus the wrapper formatting (`css`, `classname`, `attrs`, `style`) shared with `LayoutOverlay`. Overlay `css` is appended to the preset's CSS rather than replacing it.
`sizes` is flex-only and is rejected on a page-box overlay; use a columns/grid preset, or `grid_template` for explicit tracks.
As with every overlay, these can live in notebook metadata instead of YAML:
```jsonc
// notebook.metadata.nbprint
{
"layout_overlays": [
{"match": {"tag": "pair"}, "wrapper": "page-box", "layout": "columns-2", "gap": "1rem"}
]
}
```
## Footnotes
Wrap a note in `