Skip to content

How Studio works ​

Studio joins two things you write: a notebook and a view project. This page uses the view dashboard of analysis.py to show what each part does and which data passes between them.

  1. You save index.html in Source, in your own editor, or through a coding agent.
  2. Studio copies the build inputs into a snapshot and calls the provider's build().
  3. The provider writes the page: an HTML entry file and the files it loads, with empty hosts.
  4. Preview shows the new page. The session is still running, so its results fill the hosts at once.
The view project and the notebook meet in the page. Choose an event to see which parts take part and what passes between them.

The page sits between two sources that change at different times. Saving view source runs a build and publishes a new page. Changing a control reruns notebook cells and sends new results to the page that is already open, with no build. A Typst or LaTeX view adds a third path, in which the provider compiles a new PDF from the new results.

The notebook runs in a session ​

analysis.py is an ordinary marimo notebook. When someone opens the dashboard, marimo runs the notebook in a session and keeps every cell's results. Three of its cells, abridged:

python
@app.cell
def region_control(mo):
    region = mo.ui.dropdown(
        ["North", "South"], value="North", label="Region"
    )
    region
    return (region,)


@app.cell
def quarterly_section(mo, orders, plot_quarters, region):
    quarterly_chart = plot_quarters(orders, region.value)
    mo.vstack([
        mo.md("### Orders per quarter"),
        quarterly_chart,
        mo.md("Source: order log"),
    ])
    return (quarterly_chart,)


@app.cell
def summary(orders, region):
    rows = orders[orders["region"] == region.value]
    summary = {"orders": len(rows)}
    return (summary,)

The notebook holds every line of Python the dashboard depends on. Studio reads results from the session and leaves the cells as they are.

The view project holds the presentation ​

The dashboard's files live in their own folder beside the notebook:

text
analysis.py
studio/analysis/
  dashboard/
    view.toml     names the provider that builds the view
    index.html    the page source
    AGENTS.md     conventions for coding agents

The view project is an ordinary project in its format, here a plain HTML page. index.html lays out the page and names the notebook results it shows:

html
<main id="app-shell">
  <h1>Regional orders</h1>
  <marimo-cell name="region_control"></marimo-cell>
  <marimo-output value="quarterly_chart"></marimo-output>
  <p>Orders: <strong mo-value="summary.orders"></strong></p>
</main>

<marimo-cell>, <marimo-output>, and the mo-value attribute are hosts: elements that mark where a notebook result goes. They belong to the page. The notebook never contains them.

Edit the view project in Studio's Source pane, in your own editor, or with a coding agent. Studio watches the folder and builds again when you save a build input, such as index.html. A second view, such as a Typst report, gets its own folder beside dashboard/.

A view provider builds the page ​

view.toml names the view provider:

toml
schema = 1
provider = "marimo-studio/vanilla"

A view provider is Python code that knows one format. For each build, it receives a copy of the view project's build inputs and writes a page: an HTML entry file and the files it loads. The HTML provider copies the page. The React provider type-checks and bundles the app with Deno. The Typst provider prepares a document to compile.

A build never runs the notebook, so the page it writes holds empty hosts. A failed build leaves the last good page in Preview and shows the error beside the source.

Hosts receive results while the page is open ​

When the page opens, Studio connects it to the session. For each host, Studio resolves the name to the cell that computes it, runs that cell and the cells it depends on, and sends the result to the host. Pick a region in either pane of this figure, or point at a host to find its cell:

Notebookanalysis.py
region = mo.ui.dropdown(    ["North", "South"],    value="North",    label="Region",)region
quarterly_chart = plot_quarters(    orders, region.value)mo.vstack([    mo.md("### Orders per quarter"),    quarterly_chart,    mo.md("Source: order log"),])

Orders per quarter

Q1 Q2 Q3 Q4

Source: order log

rows = orders[    orders["region"] == region.value]summary = {"orders": len(rows)}
HTML pagedashboard/index.html

Regional orders

Orders: 534

<main id="app-shell">  <h1>Regional orders</h1>  <marimo-cell name="region_control"></marimo-cell>  <marimo-output value="quarterly_chart"></marimo-output>  <p>Orders: <strong mo-value="summary.orders"></strong></p></main>

<marimo-cell name="region_control">

Everything the cell shows, as marimo renders it, here the live dropdown. Both panes show one control, so picking a region in either reruns the cells that read it.

A notebook and a view that places three of its results. Choose a kind of result, or point at a cell or a host, to see what connects them.
HostThe page receives
<marimo-cell name="region_control">The cell as marimo renders it, with its live dropdown
<marimo-output value="quarterly_chart">The rich representation of the variable quarterly_chart alone, here a figure, or an image of it when the host sets accept
mo-value="summary.orders"The value as JSON. Text, a number, or a boolean appears in the element as text, and page code reads any value from its marimoValue property

A host for an output names a variable, so it can take one piece of what a cell shows. quarterly_section shows a stack with a heading, the chart, and a caption. <marimo-output value="quarterly_chart"> places only the chart, while <marimo-cell name="quarterly_section"> would place the whole stack.

A dataframe reaches page code as a FlechetteTable, which Studio decodes from Arrow IPC. Python objects stay in the session.

When a reader picks South, the browser sends the dropdown's new value to the session. marimo reruns quarterly_section and summary, the cells that read it, and Studio sends their new results to the hosts that show them. Each view reads its results from the notebook, so every view agrees with it. Place notebook results in a view covers each kind of host.

Renders turn results into files ​

A PDF holds no live elements, so a Typst or LaTeX view reads results while it compiles:

typst
#import "marimo.typ": marimo_output, marimo_value

The #marimo_value("region.value", default: "selected") region placed
#marimo_value("summary.orders", default: 0) orders.

#marimo_output("quarterly_chart", width: 100%)

The provider declares this file as a render. When a result the render reads changes, Studio calls the provider with values in their JSON form and outputs as media such as images, and the provider compiles a new PDF with its own tool. The view's page shows the PDF with a download link.

The provider also reports where each result landed in the PDF, as render regions. Studio uses them to trace a figure or a table cell inside the PDF back to the cell that computed it, as Select results with Lens shows.

Where the session runs ​

The runtime decides where the notebook's Python runs. The view project stays the same for each:

RuntimeThe session runs
PythonOn a server, through marimo edit or marimo run
BrowserIn each visitor's browser, through Pyodide
PreparedOn your machine, during export, once per prepared state

A prepared export runs no Python while visitors read it. Studio saved each prepared state's results during export, and the page moves between them. Run or export a view compares the runtimes.

Next steps ​