Add a view provider
A view provider is a Python package that teaches Studio one format. Install one in the notebook's environment, and its starters appear in the New view dialog beside the built-in ones:

This page builds acme-views, the package in that dialog. Acme report starts a plain HTML page and needs only Python. Acme dashboard adds a Lit component bundled by Vite.
Advanced feature
The view provider API is an advanced extension point. Pin marimo-studio to the minor line you test, such as marimo-studio>=0.4,<0.5.
What a provider receives and returns
A provider works with the files of a view project. Studio calls its methods at fixed moments and validates each record they return:
| When | Studio calls | The provider receives | The provider returns |
|---|---|---|---|
| The New view dialog opens | availability(), starters() | Nothing | Whether it can run, and its starters |
| Someone creates a view | create() | The chosen starter and the saved notebook's cells | The first files of the view project |
| Source opens, or a file in the view project changes | inspect() | The view project | Its source documents, build inputs, hosts, and renders |
Preview, marimo run, or an export needs a new page | build() | A build snapshot of the build inputs | A page: an HTML entry file and the files it loads |
| A notebook result that a render reads changes | render() | The render template, values as JSON, and outputs and cells as media, such as images or PDFs | Rendered files, such as a PDF |
A provider never runs the notebook. build() writes a page whose hosts are still empty, and Studio fills them from the session while the page is open, as How Studio works describes. render() is the one method that receives notebook results, and they arrive already converted to JSON and images.
Choose a pattern
Every provider imports one module, marimo_studio.view_providers. Pick the pattern closest to your tool:
| Pattern | The build | SDK pieces | Example |
|---|---|---|---|
| HTML page | copies or templates HTML | html_sites, copy_inputs | this guide's ReportProvider |
| Framework build | bundles JavaScript with a pinned toolchain | request.runner, request.work_root, request.cache_root | deno_react |
| Command-line tool | runs an installed tool that writes HTML | probe_tool, copy_inputs, ProviderError | quarto |
| Rendered file | renders files, such as a PDF, from notebook results | Render, render_page, render() | typst |
When no pattern fits, start from the HTML page provider and replace build() with your tool's steps. The built-in Quarto provider also imports private _builtin.quarto helpers, so copy its use of the public module and keep your provider's imports to marimo_studio.view_providers.
Ask a coding agent
Open a coding agent in the notebook's project and paste this prompt:
Read https://marimo-team.github.io/marimo-studio/guide/view-providers.md
and https://marimo-team.github.io/marimo-studio/reference/provider-api.md.
Create the guide's acme-views package in ./acme-views for analysis.py, with
the Acme report starter and the Acme dashboard starter, a Vite page with a
Lit header and every output cell. Follow "Add a framework build" and the
"Provider rules". Install the package with `uv add --editable ./acme-views`,
run every command in "Verify the provider", and fix each failure before
you stop.For another framework, replace Lit with its name, and see Swap Lit for a JSX framework. The rest of the page builds the same package by hand.
Files in a view project
Pick Acme report and name the view briefing. Studio creates the view project in the notebook's views folder, studio/analysis/ by default:
studio/analysis/briefing/
view.toml names the provider that owns this view
index.html the page, with one <marimo-cell> host per output cell
AGENTS.md instructions for coding agents that edit this viewThe provider writes every file except view.toml and decides how Studio treats it. A source document appears in Studio's Source pane for editing. A build input rebuilds the page when it changes.
| File | Written by | Source document | Build input |
|---|---|---|---|
index.html | the provider, at creation | yes | yes |
AGENTS.md | the provider, at creation | yes | no |
DESIGN.md | the user or an agent, later | yes, once it exists | no |
view.toml | Studio | CLI and Python only | yes |
AGENTS.md tells a coding agent which file holds the page, what the <marimo-cell> hosts mean, and where decisions belong. The marimo-studio agent skill reads it before editing a view, so ship one written for your framework. DESIGN.md records lasting decisions such as audience, visual direction, and interaction priorities once the view has a direction. Editing either file never rebuilds the page.
How Studio finds the provider
The package registers the provider in the marimo_studio.view_providerentry point group, the Python packaging mechanism that lets an installed package publish objects to other packages. Studio derives these names from the registration:
| Name | Value | Made from |
|---|---|---|
| Registration | report = "acme_views:provider" | Entry point in the acme-views package |
| Provider key | acme-views/report | Distribution name and registration name |
| Starter ID | acme-views/report:default | Provider key and the starter key |
view.toml | provider = "acme-views/report" | Written to the view project at creation |
Studio reads the entry points once per process, from its own Python environment. Restart marimo edit after you install a provider or change its starters. An unavailable provider's starters appear disabled with the recovery action it returned.
Create the package
From the notebook's project, create a library with uv, the Python package manager Studio uses to resolve notebook environments:
uv init --lib acme-views
cd acme-views
uv add "marimo-studio>=0.4,<0.5"When the notebook's directory is a uv project, uv init adds acme-views to its workspace. Register the provider in acme-views/pyproject.toml:
[project.entry-points."marimo_studio.view_provider"]
report = "acme_views:provider"Write the provider
Replace src/acme_views/__init__.py with:
import html
from pathlib import PurePosixPath
from marimo_studio.view_providers import (
BuildRequest,
BuildResult,
InspectionRequest,
BuildInput,
ProjectInspection,
ProviderAvailability,
ProviderInfo,
ProviderStarter,
SourceDocument,
StarterContext,
StarterPlan,
copy_inputs,
html_sites,
)
ENTRY = PurePosixPath("index.html")
AGENTS = PurePosixPath("AGENTS.md")
AGENTS_SOURCE = """# Acme view
Studio builds this view from `index.html` with the Acme provider.
- Each `<marimo-cell name="...">` in `index.html` shows the notebook cell with
that name. Move or remove hosts freely and keep their `name` values.
- Keep calculations in the notebook, and layout and wording in this project.
- Record audience and visual decisions in `DESIGN.md`.
"""
class ReportProvider:
info = ProviderInfo(
title="Acme report",
summary="One HTML page with the notebook's output cells.",
)
starter = ProviderStarter(
key="default",
title="Acme report",
summary="Start from one HTML page with every output cell.",
documents=(ENTRY, AGENTS),
)
def availability(self) -> ProviderAvailability:
return ProviderAvailability(True)
def starters(self) -> tuple[ProviderStarter, ...]:
return (self.starter,)
def create(self, starter: ProviderStarter, context: StarterContext) -> StarterPlan:
cells = context.output_cells
hosts = "\n".join(
f' <marimo-cell name="{html.escape(item.target)}"></marimo-cell>'
for item in cells
)
page = f"""<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>{html.escape(context.title)}</title></head>
<body>
<main id="app-shell">
{hosts}
</main>
</body>
</html>
"""
files = {ENTRY: page.encode(), AGENTS: AGENTS_SOURCE.encode()}
return StarterPlan(files=files, cell_targets=cells)
def inspect(self, request: InspectionRequest) -> ProjectInspection:
source = (request.project.root / ENTRY).read_bytes()
sites, diagnostics = html_sites(ENTRY, source)
return ProjectInspection(
documents=(SourceDocument(ENTRY, "html", "edit"),),
inputs=(BuildInput(ENTRY, "file"),),
sites=sites,
diagnostics=diagnostics,
)
def build(self, request: BuildRequest) -> BuildResult:
copy_inputs(request, request.staging_root)
return BuildResult(ENTRY)
provider = ReportProvider()Each method answers one moment from What a provider receives and returns.
availability() and starters(): fill the New view dialog
ReportProvider needs only Python, so availability() always returns ProviderAvailability(True). A provider that runs an installed tool returns probe_tool(("tool", "--version"), minimum=..., install=...), which reports a missing or outdated tool with your install instructions.
starters() returns one ProviderStarter. Its title names the starter in the list, and summary and documents fill the starter's details. Add a format, an SVG icon, a preview screenshot, a homepage, and settings to present the starter the way the built-in ones are, as Present a starter in the New view dialog describes. Studio keeps the starter list for the life of the process.
create(): write the starting files
create() receives the saved notebook in context.notebook and returns every file of the new project in a StarterPlan. Studio writes the files. When creating the view adds Studio configuration or cell aliases to the notebook, Studio calls create() again with the configured notebook, so return the same plan for the same notebook. ReportProvider writes one <marimo-cell> host into index.html for each cell in context.output_cells, and adds AGENTS.md.
context.output_cells lists the cells that may display output. It leaves out disabled cells and their descendants, because marimo never runs them.
context.cell_targets supplies the name each host uses. A named cell such as summary keeps its name. For an unnamed cell, Studio proposes an alias such as cell-2 and saves it in the notebook when the provider returns that target in StarterPlan.cell_targets.
inspect(): describe the project
inspect() returns a ProjectInspection with the project's documents, build inputs, and projection sites. Studio also calls it before each build.
documents lists the Source documents, here index.html. Studio adds view.toml, AGENTS.md, and DESIGN.md itself. inputs lists the build inputs, so Studio rebuilds when index.html or view.toml changes. For a project with many files, project_files(request.project) lists every file that can affect a build.
sites holds one projection site per <marimo-cell>, <marimo-output>, or mo-value host, the spot in the page that shows the result it names. html_sites() records each host's line and column for Source diagnostics, and reports a malformed host.
build(): publish the page
request.project.root holds a snapshot of the build inputs, in which Studio has already marked each site's host so it can connect the host to the notebook. The index.html in Source keeps its original markup. ReportProvider copies its inputs to request.staging_root with copy_inputs(). Studio validates the staged files and publishes them as a new revision. A failed build leaves the last good revision in Preview.
Install it and create a view
From the notebook's project, install the package and check that Studio finds it:
uv add --editable ./acme-views
uv run marimo-studio doctor acme-views/reportavailable acme-views/report
distribution acme-views
registration report
installed 0.1.0
One HTML page with the notebook's output cells.
starters acme-views/report:defaultStart uv run marimo edit analysis.py, open the view menu, and choose New view. Acme report appears under From acme-views, with Files created listing index.html, AGENTS.md. Or create the view from the CLI:
uv run marimo-studio view create briefing \
--target analysis.py \
--starter acme-views/report:defaultSource shows one host per output cell in the page. Give the view a design direction by saving studio/analysis/briefing/DESIGN.md:
# Design
Audience: the weekly operations review.
Lead with the threshold control, then its result.Studio lists DESIGN.md at its next inspection, such as after the next save of index.html:

Every environment that edits, builds, or exports the view needs the provider package. Publish it to a package index, or declare it as a path or Git source in the notebook's project.
Add a framework build
Acme dashboard bundles a Lit component with Vite, a frontend build tool that needs a JavaScript runtime. The machine that opens the notebook may lack Node.js, so the provider uses Deno, a JavaScript runtime that also installs npm packages. The deno package on PyPI installs it into the same environment as Studio, and deno.find_deno_bin() returns its path.
Add the dependency from acme-views and register a second provider:
uv add deno[project.entry-points."marimo_studio.view_provider"]
report = "acme_views:provider"
vite = "acme_views.vite:provider"Pin the dashboard's npm packages
The dashboard starter copies three files from src/acme_views/dashboard/ into every new view.
main.js defines the Lit header:
import { html, LitElement } from "lit";
customElements.define(
"acme-header",
class extends LitElement {
render() {
return html`<h1>Acme dashboard</h1>`;
}
},
);deno.json pins Lit and Vite:
{
"nodeModulesDir": "auto",
"imports": {
"lit": "npm:lit@3.3.1",
"vite": "npm:vite@7.1.7"
}
}Generate deno.lock beside them:
cd src/acme_views/dashboard
uv run -- deno install --lockfile-onlyThe lockfile records the version and checksum of every package in the dependency tree. Rerun the command after you change deno.json.
Write the Vite provider
Create src/acme_views/vite.py:
import os
from dataclasses import replace
from importlib.resources import files
from pathlib import Path, PurePosixPath
from deno import find_deno_bin
from marimo_studio.view_providers import (
BuildRequest,
BuildResult,
InspectionRequest,
BuildInput,
ProjectInspection,
ProviderAvailability,
ProviderCommandResult,
ProviderError,
ProviderInfo,
ProviderStarter,
SourceDocument,
StarterContext,
StarterPlan,
copy_inputs,
)
from acme_views import AGENTS, ENTRY, ReportProvider
MAIN = PurePosixPath("main.js")
CONFIG = PurePosixPath("deno.json")
LOCK = PurePosixPath("deno.lock")
TEMPLATE = files("acme_views") / "dashboard"
def installed(work: Path, pattern: str) -> str:
"""Return the resolved paths of installed npm files that match `pattern`."""
paths = sorted((work / "node_modules" / ".deno").glob(pattern))
if not paths:
raise ProviderError(f"deno install provided no {pattern}.")
return ",".join(str(path.resolve()) for path in paths)
def check(step: str, result: ProviderCommandResult) -> None:
if result.returncode != 0:
raise ProviderError(
f"{step} failed with exit code {result.returncode}: "
f"{result.stderr.strip()[-2000:]}",
hint="Fix the error in the view's source, then build again.",
code="vite-build-failed",
)
class ViteProvider(ReportProvider):
info = ProviderInfo(
title="Acme Vite",
summary="A Vite page with Lit components and the notebook's output cells.",
)
starter = ProviderStarter(
key="default",
title="Acme dashboard",
summary="Start from a Vite page with a Lit header and every output cell.",
documents=(ENTRY, AGENTS, MAIN, CONFIG, LOCK),
)
def availability(self) -> ProviderAvailability:
try:
find_deno_bin()
except FileNotFoundError:
return ProviderAvailability(
False,
reason="The deno package is not installed.",
action="Install the deno package where Studio runs.",
)
return ProviderAvailability(True)
def create(self, starter: ProviderStarter, context: StarterContext) -> StarterPlan:
plan = super().create(starter, context)
page = plan.files[ENTRY].decode()
page = page.replace(
'<main id="app-shell">\n',
'<main id="app-shell">\n <acme-header></acme-header>\n',
).replace(
" </body>",
' <script type="module" src="./main.js"></script>\n </body>',
)
project_files = {**plan.files, ENTRY: page.encode()}
for path in (MAIN, CONFIG, LOCK):
project_files[path] = (TEMPLATE / path.name).read_bytes()
return StarterPlan(files=project_files, cell_targets=plan.cell_targets)
def inspect(self, request: InspectionRequest) -> ProjectInspection:
inspection = super().inspect(request)
return replace(
inspection,
documents=(
*inspection.documents,
SourceDocument(MAIN, "javascript", "edit"),
SourceDocument(CONFIG, "json", "edit"),
SourceDocument(LOCK, "json", "read"),
),
inputs=(
*inspection.inputs,
BuildInput(MAIN, "file"),
BuildInput(CONFIG, "file"),
BuildInput(LOCK, "file"),
),
)
def build(self, request: BuildRequest) -> BuildResult:
work = request.work_root.resolve()
output = request.staging_root.resolve()
copy_inputs(request, work)
deno = find_deno_bin()
cache = {"DENO_DIR": str(request.cache_root / "deno")}
result = request.runner.run(
[deno, "install", "--frozen"],
cwd=work,
environment={**os.environ, **cache},
)
check("deno install", result)
# Stop Vite's package and workspace search at the working directory.
(work / "package.json").write_text('{ "private": true }\n', encoding="utf-8")
(work / "pnpm-workspace.yaml").write_text("packages: []\n", encoding="utf-8")
vite = [
deno,
"run",
"--cached-only",
"--frozen",
"--no-prompt",
f"--allow-read={work},{output}",
f"--allow-write={output}",
"--allow-env",
"--allow-sys=uid,osRelease",
f"--allow-ffi={installed(work, '@rollup+rollup-*/**/*.node')}",
f"--allow-run={installed(work, '@esbuild+*/**/esbuild*')}",
"node_modules/vite/bin/vite.js",
"build",
"--configLoader=native",
"--base",
"./",
"--outDir",
str(output),
]
check("vite build", request.runner.run(vite, cwd=work, environment=cache))
return BuildResult(ENTRY)
provider = ViteProvider()ViteProvider reuses the report provider and changes four methods:
availability()checks for thedenobinary, so the New view dialog shows the starter's recovery action and keeps Create view disabled when it is missing.create()copiesmain.js,deno.json, anddeno.lockinto the new view, and adds an<acme-header>element and a<script>tag toindex.html.inspect()adds the three files to Source and to the build inputs.deno.lockopens read-only.build()copies the inputs torequest.work_root, a private directory that Studio empties after the build.deno install --frozeninstalls the locked packages intonode_modulesand fails whendeno.jsonanddeno.lockdisagree. Vite then bundlesmain.jsintorequest.staging_rootand keeps the site attributes on the hosts inindex.html.
DENO_DIR keeps Deno's downloads in request.cache_root, which persists between builds, so only the first build downloads Vite and Lit. request.runner.run() stops a command when Studio cancels the build, and all commands in one build share a 120-second budget. check() turns a nonzero exit into a ProviderError that marimo-studio view build prints with the end of the command's error output.
Sandbox the build
npm packages run code inside Vite, so build() grants Vite only the Deno permissions a build needs. Deno checks the file, network, and environment access of JavaScript code against these grants:
| Flag | Allows |
|---|---|
--allow-read with the work and output | reading the working copy and the built files |
--allow-write with the output | writing the built page |
--allow-env, --allow-sys=uid,osRelease | the environment and system details Vite reads |
--allow-ffi with Rollup's .node binding | loading Rollup's native bundler |
--allow-run with the esbuild binary | starting esbuild, which transforms the code |
Rollup's native binding and the esbuild process run outside those checks, with the operating-system authority of the Studio process, as Deno's permissions that bypass the sandbox describes. deno.lock pins both, so review a lockfile change before a build installs it.
The Vite command receives DENO_DIR as its whole environment, so tokens and other variables from the Studio process stay out of the build. --cached-only and --frozen limit it to the packages deno install placed. The package.json and pnpm-workspace.yaml that build() writes stop Vite's project root search from reading parent directories, and --configLoader=native loads a vite.config.js without writing a temporary file.
Swap Lit for a JSX framework
To build the dashboard header with Preact, change the starter in four places:
Name the entry module
main.jsx, point the<script>tag at it, and list it with thejavascriptreactlanguage.Add
vite.config.jstosrc/acme_views/dashboard/, the filescreate()copies,ProviderStarter.documents, and the inspection'sdocumentsandinputs:jsexport default { esbuild: { jsx: "automatic", jsxImportSource: "preact" } };Replace Lit with
"preact": "npm:preact@10.27.2"indeno.jsonand regeneratedeno.lock. Vite resolves subpaths such aspreact/hooksfromnode_modules.Render components into their own element, such as
<div id="acme-header">, and keep the<marimo-cell>hosts outside the component tree so the framework never replaces them.
To place hosts inside components, extend inspect() to report them as ProjectionSite records from the component source, as the built-in providers in view_providers/_builtin do for JSX and Svelte.
Publish a rendered file
A provider can declare a render: files it makes from one project file and notebook results with its own tools. Its page shows the render with <marimo-output render="card.svg">, named by that project file, and Studio renders it again whenever a notebook result it reads changes. The host shows the main file, such as a PDF, an image, or a video, with a download link.
Register card = "acme_views.card:provider", then create src/acme_views/card.py:
from pathlib import PurePosixPath
from xml.sax.saxutils import escape
from marimo_studio.view_providers import (
BuildInput,
BuildRequest,
BuildResult,
InspectionRequest,
ProjectInspection,
ProviderAvailability,
ProviderInfo,
ProviderStarter,
Rect,
Render,
RenderFile,
RenderRegion,
RenderRequest,
RenderResult,
RenderValue,
SourceDocument,
SourceLocation,
StarterContext,
StarterPlan,
copy_inputs,
render_page,
)
CARD = PurePosixPath("card.svg")
TEMPLATE = """<svg xmlns="http://www.w3.org/2000/svg" width="240" height="80">
<text x="16" y="48">Total: {{ total }}</text>
</svg>
"""
class CardProvider:
info = ProviderInfo(
title="Acme card",
summary="An SVG card with the notebook's report total.",
)
starter = ProviderStarter(
key="default",
title="Acme card",
summary="Start from an SVG card that shows report.total.",
documents=(CARD,),
)
def availability(self) -> ProviderAvailability:
return ProviderAvailability(True)
def starters(self) -> tuple[ProviderStarter, ...]:
return (self.starter,)
def create(self, starter: ProviderStarter, context: StarterContext) -> StarterPlan:
return StarterPlan(files={CARD: TEMPLATE.encode()}, cell_targets=())
def inspect(self, request: InspectionRequest) -> ProjectInspection:
return ProjectInspection(
documents=(SourceDocument(CARD, "xml", "edit"),),
inputs=(BuildInput(CARD, "file"),),
renders=(
Render(
CARD,
values=(RenderValue("report.total", SourceLocation(CARD, 2, 30)),),
),
),
)
def build(self, request: BuildRequest) -> BuildResult:
copy_inputs(request, request.template_root)
return BuildResult(render_page(request, CARD))
def render(self, request: RenderRequest) -> RenderResult:
template = (request.template_root / request.source).read_text("utf-8")
total = request.values.get("report.total")
text = "pending" if total is None else str(total)
card = template.replace("{{ total }}", escape(text))
(request.output_root / CARD).write_text(card, encoding="utf-8")
# The total sits across the middle of the card, so Lens can select it.
total_region = RenderRegion(
CARD,
Rect(x=0.05, y=0.35, width=0.7, height=0.35),
reads=("report.total",),
label="Report total",
)
return RenderResult(
(RenderFile(CARD, "image/svg+xml"),), main=CARD, regions=(total_region,)
)
provider = CardProvider()Render(CARD, values=...) declares that the provider renders card.svg and names the notebook values it reads, at the place it reads them. Each value arrives in its JSON form, so a dataframe arrives as a list of row objects and a date as ISO 8601 text.
build() copies the declared build inputs into request.template_root, which Studio keeps private, and writes a page with one <marimo-output render="card.svg"> host with render_page(). A provider that builds its own page places the host beside its other content and hosts.
Studio calls render() with a writable copy of the template in request.template_root, the file to render in request.source, an empty request.output_root, and the reader's current results. A result without a current value is absent and a null result is None, so the template shows its default for both. RenderResult lists the files to publish with their media types and names the main file. A render that places figures or named cell outputs reads them with RenderOutput or RenderCell. Render files from notebook results defines the contract, including figure sizes and preview files.
Make parts of the file selectable
A rendered file is a picture of results, so Studio cannot tell on its own which pixels show which result. render() returns a render region for each part the provider placed, and Lens can select that part and trace it to its cell. CardProvider returns one region, total_region, for the total across the middle of the card.
Rect measures fractions of the file's width and height from its top left corner. A PDF region also names its page, and a video region the start and end seconds of the frames that show the part. Studio drops a region that leaves its file or names no result the render reads, and warns once. Locate parts of rendered files defines every field.
Provider rules
Studio checks each result against these rules and reports the one it breaks.
- Implement every method as a synchronous function. Raise
ProviderErrorfor a problem the author can fix, and passsource=when it points at a file. - Declare every provider option in
info.options. Studio rejects a view whoseview.tomlsets any other option. - List every file the build reads in
inputs. The build snapshot holds only those files from the view project. - Keep
inspect()read-only. Put reusable data inrequest.cache_root. - Report each host as a
ProjectionSitewhoseoffsetpoints inside its start tag in the stored file.html_sites()does this for HTML. Matchkindto the host:cellformarimo-cell,outputformarimo-output,valuefor an element withmo-value. - Build from the snapshot in
request.project.rootand keep thedata-marimo-studio-siteattribute on each host in the built files. - Write the page inside
request.staging_rootand render templates insiderequest.template_root. The page needs onehead, onebody, and one element withid="app-shell". - Declare each render by its source file, a build input, and show only those files in
<marimo-output render="...">hosts. - Run
build()commands withcwdinsiderequest.project.root, which holdsrequest.work_root,request.staging_root, andrequest.template_root. Runrender()commands insiderequest.template_root. - Pass
environment={**os.environ, ...}to add variables. A mapping replaces the command's whole environment. - Pin npm packages in a lockfile that ships with the starter, and install them with
deno install --frozen. - Run build tools that execute npm code with scoped Deno permissions, as in Sandbox the build.
- Use a kebab-case diagnostic
codeand a non-emptymessagewithout leading or trailing whitespace.
Studio rebuilds a view when its inputs, the provider package version, or the tool version from availability() change. Release a new version when the build output changes for the same inputs.
Verify the provider
Add check_provider() to the provider's tests. It runs each starter through Studio's steps and raises ProviderCheckError with the message Studio would show:
from marimo_studio.view_providers.testing import check_provider
from acme_views import provider
def test_report_provider() -> None:
(view,) = check_provider(provider)
assert "index.html" in {path.as_posix() for path in view.published}Pass the provider object while you develop it, or the installed key, such as check_provider("acme-views/report"), to load it the way Studio does. Pass options={"entrypoint": "page.html"} to check a provider option. check_provider() lists every check.
Then check the installed provider end to end from the notebook's project:
uv run marimo-studio doctor acme-views/vite
uv run marimo-studio view create dash --target analysis.py \
--starter acme-views/vite:default
uv run marimo-studio view build dash --target analysis.py
uv run marimo run analysis.py| Command | Expected result |
|---|---|
doctor acme-views/vite | First line available acme-views/vite, exit status 0 |
view create | Created view dash |
view build | Built dash for development use |
marimo run | http://localhost:2718/dash/ shows the Lit header and the notebook's cells |
marimo run builds the production profile on the first request. The provider works when /dash/ shows the notebook's controls and outputs and no This section is unavailable. placeholder.
Troubleshoot
| Message or symptom | Fix |
|---|---|
Unknown view provider 'acme-views/vite' | Install the package in the environment that runs Studio, check the entry point, and restart marimo edit. |
view.toml sets 'port', which Acme Vite does not read. | Add the option to info.options, or remove it from view.toml. |
Provider working directory is outside the view snapshot | Run commands in request.work_root or request.project.root. |
expected one element with id="app-shell" | Keep one #app-shell element in the entry document. |
projection-site-missing, and the page shows This section is unavailable. in place of a cell | Build from request.project.root and keep each host's data-marimo-studio-site attribute. |
The View provider API defines every record and term on this page.