flowchart TB
A[Excel assumptions / source tables] --> B[bf.inputs]
B --> C[Validation + normalization]
C --> D[Domain model]
D --> E[Controls / charts / explanations]
D --> F[bf.publish]
F --> G[BF.OUTPUT]
F --> H[BF.FUNCTION]
G --> I[Excel reports / formulas]
H --> I
Building Notebooks
Boardflare uses marimo as the notebook surface and Pyodide as the browser Python runtime. This guide covers the notebook concepts that matter once you move beyond the starter example.
Reactive notebook basics
Boardflare uses marimo, a reactive Python notebook. It looks familiar if you have used notebooks before, but its execution model is intentionally different from an execution-history notebook.
Cells form a dependency graph
Consider three cells:
price = 100discount_rate = 0.10net_price = price * (1 - discount_rate)
net_priceThe last cell depends on variables defined upstream. If price or discount_rate changes, marimo reruns the dependent calculation so the displayed result stays consistent with the source.
You normally should not rely on “run these cells in this historical order.” Structure the notebook so dependencies are visible in the variables each cell reads and defines.
Workbook values are reactive inputs too
Boardflare’s displayed bf.inputs() widget connects Excel references to the same notebook dependency graph:
inputs = bf.inputs(
sales=bf.ref("Sales!A1:D20", headers=True),
)
inputssales = inputs["sales"]When the workbook range changes, dependent notebook cells update.
Notebook controls participate in the same graph
Marimo UI elements such as sliders, dropdowns, tables, and buttons can be normal reactive inputs:
scenario = mo.ui.dropdown(
options=["Base", "Upside", "Downside"],
value="Base",
label="Scenario",
)
scenarioA downstream cell can read scenario.value; changing the control reruns affected cells without callback plumbing.
This is why the same notebook can serve both exploratory analysis and, when useful, an app-style presentation.
Display Boardflare bridge widgets
Boardflare calls return Anywidget models that must remain displayed:
inputs = bf.inputs(...)
inputsand:
publication = bf.publish(...)
publicationThe displayed models own the live connection to the workbook. Treat them as integration endpoints, not decorative outputs.
Keep notebook stages clear
For a substantial analysis, a useful shape is:
workbook inputs
↓
validation / normalization
↓
model / transformation
↓
diagnostics / charts / controls
↓
optional publication to Excel
See Designing notebooks for a complete pattern.
Source is Python
Marimo notebooks are stored as Python source. That makes the notebook a normal source artifact rather than an opaque execution-history file. Boardflare saves that source with the Excel workbook so the analysis can be reconstructed when the workbook reopens.
Write notebook code with an AI assistant
An AI assistant such as ChatGPT, Claude, or Copilot can draft and revise notebook code. Provide it the canonical Boardflare Notebook Reference (or llms-full.txt), which documents marimo reactivity, the boardflare API, and pre-bundled packages. Give it the workbook ranges it should read and what it should publish back to Excel. Then review the code, paste it into the notebook, run it, and save it.
The assistant does not see your workbook or run the notebook. Review generated code as carefully as code you wrote yourself, including imports, data handling, and published results. The notebook, not the assistant, is the runtime, so the durable result is ordinary Python source with explicit workbook inputs and published outputs. In Excel, assistants running in Office can also inspect and edit notebook cells directly on the hidden _BOARDFLARE worksheet and signal the add-in to reload by incrementing cell H1 and reading the response JSON in H2 (see Workbook Editing: Code Sheet & AI Handshake).
Learn more about marimo
Boardflare documents the Excel integration contract. For the complete marimo programming model, editor features, UI elements, and reactive notebook practices, use the official marimo documentation.
Designing maintainable notebooks
A maintainable Boardflare notebook has a clear boundary between the Excel workbook, the Python analysis, and the results the workbook actually needs to consume.
Keep one upstream data boundary
Declare workbook dependencies together in one displayed bf.inputs() widget so the notebook’s connection to Excel is easy to inspect.
inputs = bf.inputs(
transactions=bf.ref("Data!A1:G500", headers=True),
scenario="Control!B2",
discount_rate="Control!B3",
)
inputsUse sheet-qualified references in multi-sheet workbooks. Unqualified A1 references follow the host’s active-sheet behavior, which is usually less explicit for a workbook intended to be shared.
Refactor the boundary, not every formula
A common migration mistake is to move an existing workbook into Python cell by cell. Instead, first define what Excel should continue to own and where Python should begin.
Suppose a workbook currently has:
Inputs sheet
B2 = starting revenue
B3 = monthly growth
B4 = churn
B5 = months
Forecast sheet
dozens of copied formulas implementing the recurring model
A cleaner workbook/notebook boundary is:
inputs = bf.inputs(
starting_revenue="Inputs!B2",
monthly_growth="Inputs!B3",
churn="Inputs!B4",
months="Inputs!B5",
)
inputsThen keep the model in a normal function:
def build_forecast(starting_revenue, growth, churn, periods):
value = float(starting_revenue)
rows = [["Period", "Revenue"]]
for period in range(1, int(periods) + 1):
value *= 1 + float(growth) - float(churn)
rows.append([period, value])
return rows
forecast = build_forecast(
inputs["starting_revenue"],
inputs["monthly_growth"],
inputs["churn"],
inputs["months"],
)Excel still owns the visible assumptions and can still own reconciliations and presentation formulas. Python owns the repeated model logic.
Let marimo manage dependency order
Marimo builds a dependency graph from cell references. Put calculations in downstream cells and avoid hidden mutable state or callbacks that recreate manual notebook execution order.
A useful rule is one owner per public variable: define an important value in one cell and let downstream cells reference it. Do not repeatedly redefine the same variable across cells.
Separate model logic from notebook UI
Keep domain calculations in ordinary Python functions and use marimo UI components for interactive controls. This makes the analytical logic easier to understand and test independently of presentation.
Use worksheet cells for durable business assumptions; use notebook controls for transient exploration such as scenario, risk multiplier, or visualization choices.
The current Revenue Command Center follows that split:
Drivers sheet assumptions
│
├── starting MRR
├── growth / churn
├── margin / opex
└── target / simulation count
│
▼
Reactive Python model
▲
│
Notebook controls
scenario / growth lift / risk multiplier
Validate at the boundary
Do not let malformed workbook data silently flow into a consequential model. Validate as soon as workbook values enter Python.
required = {"SKU", "Demand", "Demand Std", "Lead Time"}
missing = required.difference(inputs["skus"].columns)
if missing:
raise ValueError(f"Missing required columns: {sorted(missing)}")
service_level = float(inputs["service_level"])
if not 0.5 <= service_level < 1.0:
raise ValueError("Service level must be between 0.5 and 1.0")For finance/accounting workflows, add control totals and reconciliation assertions before publication:
source_total = float(source["Amount"].sum())
output_total = float(cleaned["Amount"].sum())
if abs(source_total - output_total) > 0.01:
raise ValueError("Control total changed during transformation")Useful checks include required cells/columns, expected types, bounded assumptions, duplicate identifiers, reconciliation totals, infeasible constraints, and explicit handling of missing values.
Organize the notebook for maintenance
For a nontrivial notebook, this sequence is usually easier to maintain:
- Title and explanation — explain what the analysis does, its assumptions, and what the user may change.
- Imports — standard and third-party packages.
- Notebook controls — transient UI choices when interaction helps the analysis.
- Workbook input registry — one displayed
bf.inputs()cell for reactive workbook inputs. - Normalization/validation — convert workbook values into model-ready structures.
- Domain model — deterministic functions and calculations.
- Presentation — tables, charts, explanations, exception queues.
- Publication — one displayed
bf.publish()cell with the complete registry.
Do not mix workbook bridge calls throughout every analytical cell. Keeping the input and publication boundaries obvious makes the notebook easier to review.
Keep one downstream publication registry
publication = bf.publish(
outputs={"summary": summary, "forecast": forecast},
functions={"scenario_price": scenario_price},
)
publicationA successful publication atomically replaces the previous output/function registry. A failed candidate publication does not partially replace the last successful one.
Published objects are live-session state. The workbook persists the source that recreates them, not the Python objects themselves.
Use outputs and functions for different jobs
Use BF.OUTPUT() for state the reactive notebook has already calculated: KPI blocks, forecasts, exception queues, fitted parameters, selected allocations.
Use BF.FUNCTION() for short callable calculations that take worksheet arguments and return a result through the live notebook model. Published functions should be bounded and non-blocking; a long synchronous callable can block the Python kernel even after its worksheet result times out.
Use App mode only when the notebook needs a simplified interface
Most notebooks can remain in Edit mode for their entire useful life. When the same analysis becomes a repeatable tool for another user, choose Open as: App so the next session emphasizes controls, explanations, and outputs.
App mode is presentation only, not an authorization boundary. Do not rely on it to protect secrets or proprietary source from a workbook recipient.
Workbook versus notebook responsibilities
| Put in the workbook | Put in the notebook |
|---|---|
| User-entered assumptions | Analytical/model logic |
| Source data already maintained in Excel | Data transformation that benefits from Python |
| Reviewable formulas and reconciliations | Statistics, simulation, optimization, specialized libraries |
| Familiar tables and reports | Reactive controls and custom visualizations |
| Final worksheet formulas | Published notebook state and callable functions |
Performance and size boundaries
Design for the actual workbook/runtime protocol rather than assuming an unlimited desktop process. For exact system limits, timeouts, payload constraints, and cell thresholds, see System Limits and Constraints.
Practical implications:
- bind the workbook ranges the model actually needs rather than whole sheets;
- prefer one vectorized/table calculation over thousands of worksheet function calls;
- publish a table as one output instead of hundreds of independent scalar outputs when possible;
- keep
BF.FUNCTION()callables short; - use external Python when the workflow is fundamentally a large batch/file/system job rather than an Excel-connected notebook.
See The Boardflare Python API for API rules and Packages and environment for browser-runtime constraints.
Testing before sharing
At minimum, test:
- representative inputs and boundary values;
- missing/invalid workbook data;
- control totals or model invariants;
- save, close, and reopen behavior;
- intended Edit/App presentation;
BF.OUTPUT()cold start;BF.FUNCTION()cold start and optional arguments;- a second user opening and using the workbook without author intervention when the notebook will be shared.
Use the Sales Scenario Analysis template as the introductory integration reference, then inspect the current templates for the published operations-optimization and scientific-modeling patterns.
Packages and environment
Boardflare’s notebook runs Python through Pyodide, a CPython distribution compiled for WebAssembly and the browser. This removes the need for a separate local Python installation but creates different compatibility boundaries from desktop Python.
For the authoritative specification of the runtime environment, Python and marimo versions, and pre-bundled companion packages, see Python Notebook Environment.
For browser and network constraints (including Web Worker isolation, memory boundaries, and Content Security Policy), see System Limits and Constraints.
Choosing the right execution environment
| Requirement | Recommended approach |
|---|---|
| Reactive workbook analysis and operator UI | Boardflare notebook |
| pandas/NumPy/SciPy work, or packages on the published list | Boardflare notebook |
| Batch processing local folders | External Python |
| Reading/writing hundreds of independent files | External Python |
| Desktop application or Office automation | External Python / appropriate Office automation |
| Unsupported compiled/native package | External or managed Python |
| Scheduled/headless system job | External automation runtime |
The practitioner research reviewed for Boardflare shows the same split: Python inside Excel is strongest for bounded analysis and interactive notebook logic; Python around Excel remains stronger for file/system automation. See What People Actually Use Python in Excel For.