docs/guides/publishing/opengraph.md
marimo can attach OpenGraph metadata to notebooks for:
marimo run <directory> galleryYou configure this metadata in your notebook file using inline script metadata (PEP 723), under [tool.marimo.opengraph].
Add an [tool.marimo.opengraph] section to your script metadata:
# /// script
# [tool.marimo.opengraph]
# title = "My notebook"
# description = "An interactive marimo app."
# ///
Available fields:
title: Title used in previews (defaults to a title-cased filename).description: Short description shown in previews.image: Optional thumbnail image. Must be either:
__marimo__/ (for example, __marimo__/assets/my_notebook/opengraph.png).If you generate thumbnails to the default location (see below), you typically do not need to set image: marimo will automatically use __marimo__/assets/<notebook_stem>/opengraph.png when it exists.
!!! note "Relative image paths"
For security, marimo only serves notebook-relative `image` paths from the notebook's `__marimo__/` directory.
If you do not provide an image, marimo can still serve a deterministic placeholder thumbnail. If a screenshot-based thumbnail exists at the default location, marimo will automatically use it.
The default thumbnail location is:
__marimo__/assets/<notebook_stem>/opengraph.png
To generate these thumbnails, use:
marimo export thumbnail notebook.py
marimo export thumbnail folder/
For execution and sandbox options (and for Playwright installation instructions), see Thumbnails.
For dynamic previews, you can provide a generator function in script metadata. Dynamic metadata executes Python while the server resolves previews, so enable it only for notebooks you trust. Run marimo run --execute-opengraph-generators folder/, or pass execute_opengraph_generators=True to marimo.create_asgi_app().
Generators must be top-level functions. They run outside normal cell execution, so values defined in regular cells are unavailable at metadata resolution time.
In script metadata, point generator at the name of a function defined in the notebook:
# /// script
# [tool.marimo.opengraph]
# description = "This description is static."
# generator = "generate_opengraph"
# ///
Then define the generator in a notebook cell:
/// marimo-embed
@app.function
def generate_opengraph(context, parent):
import datetime as dt
from pathlib import Path
from urllib.parse import quote_plus
# Merge behavior: we return `title` and `image`, so the static `description` stays intact (it's already in `parent`).
stem = Path(context.filepath).stem
label = quote_plus(f"{stem} {dt.datetime.now().isoformat()}")
title = f"{parent.title} (Dynamic)" if parent.title else "Dynamic OpenGraph"
return {
"title": title,
"image": f"https://placehold.co/1200x630/png?text={label}",
}
///
The generator return value is merged on top of the resolved "parent" metadata (declared fields + defaults). Any fields not returned by the generator are inherited from parent.
The generator may accept 0, 1, or 2 positional arguments:
generate_opengraph()generate_opengraph(context)generate_opengraph(context, parent)Type hints:
context: marimo._metadata.opengraph.OpenGraphContextparent: marimo._metadata.opengraph.OpenGraphMetadatacontext is an object with useful runtime information:
context.filepath: absolute path to the notebookcontext.file_key: file router key (often a workspace-relative path)context.base_url: server base URL (when running with marimo run)context.mode: "run" or "edit"The generator may return:
OpenGraphMetadata instancedict with any of title, description, and imageNone (to leave the parent metadata unchanged)