Skip to content

feat: rich notebook previews through OpenGraph metadata#8097

Merged
mscolnick merged 10 commits intomarimo-team:mainfrom
peter-gy:ptr/notebook-og-metadata
Feb 4, 2026
Merged

feat: rich notebook previews through OpenGraph metadata#8097
mscolnick merged 10 commits intomarimo-team:mainfrom
peter-gy:ptr/notebook-og-metadata

Conversation

@peter-gy
Copy link
Contributor

@peter-gy peter-gy commented Feb 3, 2026

📝 Summary

Adds notebook-level OpenGraph metadata (PEP 723 + Next.js-inspired optional generator hook) with thumbnail generation plumbing. As a first use case, wired it up to improve gallery cards in marimo run <dir>.

Follow-up of #8056.

🔍 Description of Changes

  • Introduced OpenGraphMetadata for notebooks via PEP 723: [tool.marimo.opengraph] with title, description, image, and optional generator
    • image supports either an HTTPS URL or a notebook-relative path
  • Added Next.js-style merge semantics for generators: static PEP 723 fields are the base; generator can override only the fields it returns
  • Added a canonical thumbnail endpoint: GET /api/home/thumbnail?file=...
    • redirects to HTTPS images
    • serves notebook-relative images only from the notebook’s __marimo__/ directory
    • falls back to a branded placeholder SVG when no image exists
  • Inject OG tags into notebook pages (og:title, og:description, og:image)
  • Gallery cards now prefer FileInfo.opengraph (title/description/image) and fall back to the existing title-casing behavior
  • Added marimo tools thumbnails generate ... to write thumbnails to __marimo__/assets/<stem>/opengraph.png
    • default is --no-execute (fast; no cell outputs)
    • opt-in --execute to include outputs
    • opt-in --sandbox (only applies with --execute)

📸 Thumbnails

in the examples below marimo-team/learn is a local clone of https://github.com/marimo-team/learn

Auto-generated thumbnails without playwright

No file gets written to disk. The /thumbnail API endpoint calls DEFAULT_OPENGRAPH_PLACEHOLDER_IMAGE_GENERATOR to construct an SVG dynamically and serves it.

Screenshot 2026-02-03 at 14 36 25

Auto-generated thumbnails with playwright, but without notebook execution

Renders code blocks without outputs, similar to Observable, as suggested by @manzt.

 marimo tools thumbnails generate marimo-team/learn
 marimo run marimo-team/learn
Screenshot 2026-02-03 at 14 40 50

Auto-generated thumbnails with playwright, with sandboxed notebook execution for cell outputs

If you have generated thumbnails, pass --overwrite to marimo tools thumbnails to ensure they get replaced.

marimo tools thumbnails generate marimo-team/learn --execute --sandbox
marimo run marimo-team/learn  
Screenshot 2026-02-03 at 14 49 52

⚡️ Generators

Users can define custom functions to return OG metadata based on bespoke logic.

Below an example of a notebook (1) defining generator = "generate_opengraph" in PEP 723 then (2) implementing def generate_opengraph(context, parent): as @app.function to return the metadata dynamically, yielding a custom card from with image from https://placehold.co/1200x630/png.

Screenshot 2026-02-03 at 14 58 42
# /// script
# dependencies = [
#     "marimo>=0.19.0",
#     "pyzmq>=27.1.0",
# ]
# [tool.marimo.opengraph]
# description = "The description is static, but the title and image have been computed dynamically using a generator function defined within the notebook."
# generator = "generate_opengraph"
# ///

import marimo

__generated_with = "0.19.7"
app = marimo.App(width="medium")


@app.cell(hide_code=True)
def _(mo):
    mo.md("""
    # Dynamic OpenGraph Image
    """)
    return


@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 static PEP 723 description
    # remains intact, as `description` is already present in `parent`
    label = quote_plus(dt.datetime.now().isoformat())
    return {
        "title": f"Dynamic OpenGraph",
        "image": f"https://placehold.co/1200x630/png?text={label}"
    }


@app.cell(hide_code=True)
def _():
    import marimo as mo
    return (mo,)


if __name__ == "__main__":
    app.run()
@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 4, 2026 10:12am

Request Review

@github-actions github-actions bot added the bash-focus Area to focus on during release bug bash label Feb 3, 2026
Copy link
Contributor

@mscolnick mscolnick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great! few small comments

we can do this in a followup, but will need to update the docs as well.

@Light2Dark Light2Dark added enhancement New feature or request release-highlight A feature or change to call out in upcoming release notes labels Feb 4, 2026
@peter-gy
Copy link
Contributor Author

peter-gy commented Feb 4, 2026

this is great! few small comments

we can do this in a followup, but will need to update the docs as well.

Thanks for the feedback. would it be ok to update docs in a separate PR?

@mscolnick mscolnick merged commit e7ba7bc into marimo-team:main Feb 4, 2026
50 of 51 checks passed
mscolnick pushed a commit that referenced this pull request Feb 10, 2026
## 📝 Summary

Docs for #8056 and #8097.

---------

Co-authored-by: Akshay Agrawal <akshaykagrawal7@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bash-focus Area to focus on during release bug bash enhancement New feature or request release-highlight A feature or change to call out in upcoming release notes

3 participants