Skip to main content

How We Built a Reactive Python Application Runtime Inside Excel

· 7 min read

The design goal for Boardflare's new Python experience was not “put a code editor in Excel.” It was to let a technical author build a real Python application while keeping the workbook as the interface another person already knows how to use.

That requirement led to an unusual combination: Excel + a shared Office runtime + a cross-origin marimo notebook + Pyodide + Anywidget + a spreadsheet-host abstraction + worksheet outputs and functions.

This article explains the design at a high level. The maintained technical contract lives in the Python for Excel architecture and security/data-flow documentation.

Start from the artifact, not the notebook

A conventional notebook is optimized for its author. An Excel application has a different delivery problem.

Technical author


Reactive Python notebook

├── model logic
├── controls
├── validation
└── visualizations


Saved Excel workbook


Workbook operator

The operator should be able to change assumptions, use controls, and consume results without understanding notebook execution or maintaining the Python source.

That is why Boardflare separates Edit and Run presentations and why notebook source is saved with the workbook rather than living only in the author's browser session.

Excel remains the application interface

We deliberately did not start from the assumption that Excel should be replaced.

Excel is still extremely effective for:

  • structured assumptions;
  • tables and familiar formulas;
  • review and reconciliation;
  • ad hoc inspection;
  • stakeholder communication;
  • file-based distribution inside organizations.

The problem arises when the analytical logic behind that interface grows into something formulas, macros, and helper sheets are no longer good at expressing.

Boardflare keeps Excel as the interface while moving that application logic into Python.

Why marimo instead of a conventional notebook?

Spreadsheet users already expect dependency-driven recalculation. Change an input and the things that depend on it should update.

marimo is a reactive Python notebook: cells form a dependency graph, and dependent cells automatically update when their inputs change. That model fits spreadsheet applications much better than relying on a user to execute notebook cells in the correct historical order.

Boardflare adds explicit workbook dependencies:

inputs = bf.inputs(
sales=bf.ref("Sales!A1:D20", headers=True),
scenario="Assumptions!B2",
)
inputs

Downstream marimo cells depend on inputs["sales"] or inputs["scenario"] just as they would depend on any other Python value. Workbook changes therefore enter the notebook's normal reactive graph rather than creating a second hidden execution system.

Why run Python in the browser?

The end-user distribution problem is difficult if every workbook recipient must install and configure an identical local Python environment.

Marimo's browser runtime uses Pyodide, which compiles CPython and a substantial scientific Python ecosystem to WebAssembly. The workbook user therefore does not need a normal desktop Python installation to run a Boardflare notebook application.

That convenience has a clear boundary. A browser runtime is not the right place for every native dependency, unrestricted local file automation, subprocess, or desktop integration. We document those limitations rather than pretending browser Python is a universal replacement for external Python.

Keeping marimo stock

One of the most important implementation choices was to avoid making Boardflare depend on a private fork of marimo.

Marimo owns:

  • notebook editing;
  • serialization;
  • the reactive dependency graph;
  • Python execution;
  • notebook UI primitives.

Boardflare owns:

  • workbook integration;
  • saved-source persistence;
  • Excel outputs and custom functions;
  • application startup;
  • product chrome;
  • the host/notebook security boundary.

This makes the integration easier to reason about and gives us a cleaner upgrade path as marimo evolves.

Anywidget is the capability surface

Boardflare's public Python API returns Anywidget models for workbook inputs and published outputs/functions.

inputs = bf.inputs(
assumptions=bf.ref("Assumptions!A1:B8", headers=True),
)
inputs

and:

bf.publish(
outputs={"summary": summary},
functions={"discount": discount},
)

Those displayed widgets are not decorative controls. They are notebook-side endpoints for explicit capabilities owned by the surrounding Boardflare host.

That lets user code stay small and public while the host handles the spreadsheet-specific work.

The notebook does not get unrestricted access to its parent

The marimo notebook runs in a separate-origin iframe. Boardflare establishes a validated communication channel between the parent host and notebook before capabilities are provided.

At a high level:

Excel / Boardflare host

│ validated channel

separate-origin notebook iframe


marimo + Pyodide + Boardflare widgets

The current implementation validates origin/session information and then moves ongoing capability traffic onto an explicit MessageChannel port. Input, output, and source persistence are separate capabilities with their own lifecycle.

The goal is not to claim that arbitrary Python is harmless. Notebook source is executable code. The goal is to keep the integration boundary explicit instead of treating unrestricted cross-window messaging as the workbook API.

One application model, more than one spreadsheet host

We also did not want notebook code coupled directly to Office.js.

The shared @boardflare/spreadsheet-bridge package places host-specific spreadsheet behavior behind a common interface:

Boardflare notebook application

Spreadsheet Bridge
┌───┴────┐
│ │
Excel Univer

The Excel driver talks to the Office APIs used by the production add-in. The Univer driver talks to the browser spreadsheet SDK used by the standalone demo.

Univer is an embeddable office/spreadsheet framework, which makes it useful for demonstrating the same notebook integration outside a real Excel host.

The two hosts are intentionally not presented as identical. Workbook persistence and custom-function lifecycle differ. The value of the bridge is that notebook input/output behavior does not need to know which spreadsheet implementation sits underneath it.

Publishing results back into Excel

A useful workbook application cannot end at a chart in the task pane. Results need to participate in the spreadsheet itself.

Boardflare publishes two live registries:

=BF.OUTPUT("summary")

for values and tables, and:

=BF.FUNCTION("discount", A1, B1)

for short callable Python functions.

The add-in uses Microsoft's shared runtime, allowing task-pane code and Excel custom functions to share the same long-lived JavaScript runtime.

That matters during workbook startup. A worksheet can need BF.OUTPUT() before the user has manually opened the visible Python task pane. The shared runtime can mount the notebook, restore its saved source, hydrate workbook inputs, and recreate the published registry so the worksheet calculation can resolve.

Persistence is source, not a frozen Python process

Boardflare does not attempt to serialize an entire live Python interpreter into the workbook.

Instead, Excel persists the notebook source and saved startup preference. Reopening reconstructs runtime state:

Saved notebook source


marimo / Pyodide starts


workbook inputs hydrate


reactive graph calculates


outputs and functions publish

This is a more understandable contract: the source is durable; runtime state is rebuilt from it.

AI stays on the authoring side

AI is useful in this system, but it is not the runtime architecture.

The notebook assistant can generate, revise, explain, and debug Python using supplied notebook context and Boardflare's public workbook API contract. The result remains normal inspectable source that can be tested before the workbook is distributed.

We think that distinction matters for business applications. The durable application should not require a model to improvise its core logic every time an operator opens the workbook.

The design principle

The individual technologies will continue to evolve. The more durable idea is the separation of responsibilities:

Excel is the interface. Reactive Python is the application logic. Boardflare provides the runtime and bridge that let an author deliver the result as a workbook another person can operate.

For implementation details that must stay current, use the Architecture and Security and Data Flow documentation instead of treating this dated article as a specification.