flowchart TB
subgraph Excel[Excel]
WB[Workbook cells / ranges / names]
XML[Workbook Custom XML\nsaved notebook source]
CF[BF.OUTPUT / BF.FUNCTION]
Shared[Long-lived Office shared runtime]
end
subgraph Boardflare[Boardflare parent host]
UI[Task pane / Notebook surface]
Session[Notebook session + capabilities]
Bridge[Spreadsheet Bridge]
Registry[Live publication registry]
end
subgraph Notebook[Separate-origin notebook iframe]
Marimo[Stock marimo]
Pyodide[Pyodide / browser CPython]
Inputs[bf.inputs Anywidget]
Publish[bf.publish Anywidget]
end
WB <--> Bridge
XML <--> Session
CF <--> Shared
Shared --> UI --> Session
Session <-->|validated MessagePorts| Marimo
Marimo --> Pyodide
Pyodide --> Inputs
Pyodide --> Publish
Inputs <-->|workbook input capability| Session
Publish <-->|output/function capability| Session
Session <--> Bridge
Session <--> Registry
Registry <--> CF
How We Built a Reactive Python Notebook Runtime Inside Excel
The design goal for Boardflare’s Python experience was not simply “put a code editor in Excel.” It was to give substantial Python work a coherent reactive notebook while keeping the workbook directly connected as a source-data, review, output, and delivery surface.
That led to a runtime with several deliberately separate pieces: Excel’s long-lived shared runtime, a Boardflare host, a cross-origin marimo notebook, browser Python through Pyodide, Anywidget capabilities, a spreadsheet-host abstraction, workbook source persistence, and live worksheet outputs/functions. The underlying platform concepts are documented by Microsoft in its Office add-in runtime guide, by marimo as a reactive Python notebook, by Pyodide as Python compiled for the browser, and by anywidget as a widget specification and toolkit.
This article explains why those pieces exist and how they fit together. The maintained specifications live in Architecture and Runtime and Security and Data Flow.
The complete picture
A useful way to understand the system is to start with the full topology rather than with one API call:
There are three ideas behind this shape:
- Excel remains useful. We do not require every assumption, input, or deliverable to move into the notebook.
- The notebook owns the Python program. Multi-step transformations, models, controls, charts, and explanations can live in one reactive source file instead of being distributed across worksheet cells.
- The boundary is explicit. Notebook Python gets workbook capabilities through a small public API rather than receiving unrestricted access to the add-in parent.
For a workbook user, the consequence is simpler than the diagram: no separate desktop Python installation is required; declared worksheet inputs can drive reactive notebook recalculation; selected notebook results can flow back to worksheet formulas; and the saved artifact is notebook source in the workbook rather than a serialized Python process. AI authoring is optional and is not part of the calculation path.
Why marimo fits spreadsheet work
Spreadsheet users already expect dependency-driven recalculation. Change an input and downstream work should update.
marimo is a reactive Python notebook. Its documentation describes dependent cells rerunning when referenced values change and notebooks being stored as Python source rather than an opaque notebook database. That maps naturally to a workbook-connected model.
Boardflare adds workbook dependencies explicitly:
import boardflare as bf
inputs = bf.inputs(
sales=bf.ref("Sales!A1:D20", headers=True),
scenario="Assumptions!B2",
)
inputsDownstream cells read inputs["sales"] and inputs["scenario"] like ordinary Python values. When Excel changes one of those references, Boardflare refreshes the input model and marimo reruns dependent cells through its normal reactive graph.
The spreadsheet and notebook therefore do not need competing recalculation systems.
Why run Python in the browser?
A workbook is much easier to distribute when every recipient does not need to recreate the author’s desktop Python installation.
Marimo’s WebAssembly runtime uses Pyodide to run CPython in the browser. That provides a substantial Python environment without a separate local Python installation.
It also creates a clear compatibility boundary. Browser Python is not desktop/server Python. Pyodide documents limitations around local files and subprocesses, raw sockets, and browser networking such as CORS. Some native dependencies and desktop integrations therefore do not fit this environment.
That is a tradeoff, not something we try to hide: the browser runtime makes workbook distribution easier, while external Python remains a better fit for workloads that require unrestricted operating-system capabilities.
Keeping marimo stock
A major design decision was to put product-specific behavior around marimo rather than inside a private fork.
Marimo owns:
- notebook editing;
- source serialization;
- reactive dependency analysis;
- Python execution;
- notebook UI primitives.
Boardflare owns:
- Excel/shared-runtime startup;
- workbook source persistence;
- workbook input/output capabilities;
- worksheet functions;
- product presentation and App mode;
- AI provider/account policy;
- the parent/notebook security boundary.
That separation gives us a cleaner upgrade path and makes failures easier to classify. If an Excel formula cannot see a published result, for example, the question is usually about the live publication capability or startup lifecycle—not about a hidden modification to marimo internals.
Anywidget is the notebook-side capability surface
The public boardflare package returns Anywidget models for the connections that need a live browser counterpart. Anywidget’s front-end specification explicitly separates one-time model initialization from per-view rendering, which is the lifecycle distinction Boardflare relies on here.
inputs = bf.inputs(
assumptions=bf.ref("Assumptions!A1:B8", headers=True),
)
inputsand:
publication = bf.publish(
outputs={"summary": summary},
functions={"discount": discount},
)
publicationThe displayed widget is not decorative. Its model owns the capability connection. That is why Boardflare documentation tells authors to keep bf.inputs() and bf.publish() displayed as cell results.
This model/view distinction matters. A widget model can render more than one visual view without multiplying workbook subscriptions, and destroying a view does not have to mean destroying the underlying capability.
The notebook does not get unrestricted access to the parent
The marimo runtime executes in a different-origin iframe. The parent establishes separate source, input, and outputs capabilities only after validating the connection.
sequenceDiagram
participant Child as Notebook / Anywidget
participant Parent as Boardflare parent
participant Port as Dedicated MessagePort
Child->>Parent: Connect request
Parent->>Parent: Validate child window + exact origin
Parent->>Parent: Validate protocol + session + nonce
Parent->>Parent: Validate capability + generation
Parent-->>Child: Transfer MessagePort
Child<<->>Port: Ongoing capability traffic
Port<<->>Parent: Active capability only
The goal is not to claim that arbitrary Python is safe. Notebook source is executable code. The goal is to avoid making “whatever can call postMessage” equivalent to the workbook API.
The maintained Security and Data Flow page documents the current validation checks and trust boundaries in more detail.
One notebook model, multiple spreadsheet hosts
Notebook code should not care whether the spreadsheet underneath it is production Excel or the browser spreadsheet used for demos.
The shared @boardflare/spreadsheet-bridge package puts host-specific spreadsheet operations behind a common interface:
flowchart TD
Notebook[Boardflare notebook integration] --> Bridge[Spreadsheet Bridge]
Bridge --> ExcelDriver[Excel driver]
Bridge --> UniverDriver[Univer driver]
ExcelDriver --> Office[Office.js / Excel]
UniverDriver --> Univer[Univer browser workbook]
The two hosts are deliberately not described as identical. Excel has workbook Custom XML persistence and a shared custom-function runtime. The public demo has a browser page/session lifecycle. The bridge lets the notebook model share useful concepts without erasing those host differences.
Publishing selected results back to Excel
A notebook does not have to end at a chart in the task pane. It can expose a deliberately small worksheet-facing API.
For values and tables:
=BF.OUTPUT("summary")
For short callable Python functions:
=BF.FUNCTION("discount", A1, B1)
Both are backed by a live registry owned by a displayed bf.publish() model.
sequenceDiagram
participant Publish as bf.publish
participant Host as Boardflare host
participant Excel
Publish->>Host: Prepare values for workbook date system
Host-->>Publish: Prepared
Publish->>Host: Claim complete output/function registry
Host-->>Publish: Registry acknowledged
Excel->>Host: BF.OUTPUT subscription / BF.FUNCTION invocation
Host-->>Excel: Live value or Python result
The claim is atomic: a replacement publication does not tear down the prior successful registry until the replacement has validated and claimed successfully.
The publication widget can also show which worksheet formulas are actively consuming outputs/functions. The same information is available to Python through publication.consumers.
Authentication is not part of calculation startup
Another important separation is between the calculation runtime and Notebook AI eligibility.
Boardflare lets the notebook calculation runtime mount while Office authentication is still resolving. BF.OUTPUT() and BF.FUNCTION() therefore do not depend on a user opening sign-in UI.
Office identity is used separately to decide whether the Marimo AI authoring controls are enabled. At launch, Notebook AI is restricted to eligible work or school Microsoft identities; personal or unresolved identities receive the same notebook calculation runtime with AI disabled.
That separation prevents an optional authoring capability from becoming a hidden dependency of workbook calculation.
Persistence is source, not a frozen Python process
Boardflare does not try to serialize a live interpreter, DataFrames, widgets, subscriptions, or Python function objects into the workbook.
It saves the notebook source and its preferred Edit/App opening presentation. Reopening reconstructs the runtime:
flowchart TD
Saved[Saved notebook source + opening mode]
Runtime[Start marimo / Pyodide]
Inputs[Hydrate bf.inputs]
Graph[Run reactive graph]
Publish[Claim bf.publish registry]
Excel[Resolve worksheet consumers]
Saved --> Runtime --> Inputs --> Graph --> Publish --> Excel
This source-first contract is easier to reason about and makes the durable artifact compatible with ordinary source practices such as review, diffing, backup, and version control when the author chooses to surface the .py file externally.
Uploading source is intentionally staged
The add-in can open an existing Marimo .py file, but choosing a file is not a persistence operation.
stateDiagram-v2
[*] --> Saved: saved workbook source or bundled starter
Saved --> Staged: upload .py
Staged --> Saved: Marimo Save + verified workbook write
Staged --> Saved: startup failure + Restore saved notebook
The replacement source starts in a fresh Edit session. The previous workbook copy remains durable until the author explicitly saves the replacement and Boardflare verifies the workbook write. That gives a broken upload a recovery path without silently destroying the last saved notebook.
Download follows the same source-first philosophy: it exports source that Marimo has already submitted to Boardflare. It cannot magically serialize editor changes that were never saved.
The legacy function path stays separate
Boardflare still supports workbooks created with the earlier standalone Functions Editor. Those functions use workbook settings, Excel Name Manager LAMBDAs, and BOARDFLARE.EXEC.
They do not execute through the notebook registry:
flowchart LR
NotebookFormula[BF.OUTPUT / BF.FUNCTION] --> NotebookRuntime[Live notebook registry]
LegacyFormula[Legacy Name Manager function] --> Exec[BOARDFLARE.EXEC] --> LegacyWorker[Legacy runpy worker]
Keeping that boundary explicit matters for compatibility. Notebook startup or bundle changes should not accidentally become prerequisites for established BOARDFLARE.EXEC workbooks.
The design principle
The individual libraries and runtime versions will change. The more durable architecture is the separation of responsibilities:
Excel remains the workbook data, review, and delivery surface. The reactive notebook is the coherent home for substantial Python work. Boardflare supplies explicit runtime, persistence, and worksheet bridges between them.
For current implementation details, limits, and security controls, use Architecture and Runtime and Security and Data Flow rather than treating this dated launch article as a specification.