Working with Excel
Excel and the notebook are complementary surfaces. Use workbook ranges and names as reactive notebook inputs, then publish only the results or reusable calculations that Excel actually needs to consume.
Read workbook data
Use bf.inputs() to declare the Excel data a notebook depends on. Boardflare watches those references and updates the notebook when the workbook changes.
Declare inputs once
import boardflare as bf
inputs = bf.inputs(
sales=bf.ref("Sales!A1:D20", headers=True),
tax_rate="Assumptions!B2",
)
inputs
Then read the synchronized values in downstream cells:
sales = inputs["sales"]
tax_rate = inputs["tax_rate"]
Prefer one clear upstream input registry for a related analysis rather than scattering workbook references throughout many cells.
:::important Display the bf.inputs() result
The displayed Anywidget model owns the live workbook connection. Do not hide it in an assignment-only cell or replace it with a private bridge API.
:::
Reference cells and ranges
A plain reference string is enough when no options are required:
inputs = bf.inputs(
threshold="Controls!B4",
)
inputs
Use bf.ref() when you need options such as headers:
inputs = bf.inputs(
transactions=bf.ref("Data!A1:G500", headers=True),
)
inputs
Boardflare resolves supported workbook reference forms through the spreadsheet host, including A1 references, sheet-qualified ranges, and defined names. In multi-sheet workbooks, prefer sheet-qualified references so the dependency is obvious.
Understand returned values
| Workbook reference | Python value |
|---|---|
| One cell | Scalar |
| Multi-cell range | pandas DataFrame |
Multi-cell range with headers=True | DataFrame with the first row used as columns |
| Empty cell | May materialize as None |
For example:
inputs = bf.inputs(
scenario="Control!B2",
orders=bf.ref("Orders!A1:E200", headers=True),
)
inputs
inputs["scenario"] is a scalar, while inputs["orders"] is a DataFrame.
Let reactivity do the refresh work
The notebook is reactive. When Excel changes a declared dependency, Boardflare refreshes that input and marimo reruns cells that depend on it.
You normally should not build manual “refresh all Python” logic. Structure cells around explicit variables and dependencies instead.
During initial startup, Boardflare waits for the first workbook snapshot before allowing dependent calculations to proceed. This prevents downstream cells from publishing plausible-looking results based on an uninitialized placeholder.
Inspect input errors
Use:
inputs.errors
After initialization, reading an input that failed to resolve raises instead of silently returning a placeholder value.
Common causes include:
- a renamed or deleted worksheet;
- a mistyped reference;
- an invalid defined name;
- a range shape that no longer matches the notebook's assumptions.
Name inputs for the analysis
Use Python names that describe the business role of the workbook data:
inputs = bf.inputs(
actuals=bf.ref("Actuals!A1:F50", headers=True),
assumptions=bf.ref("Assumptions!A1:B12", headers=True),
tolerance="Controls!B3",
)
inputs
That makes downstream code easier to read than repeating workbook coordinates throughout the notebook.
Return results to Excel
Notebook results can stay in the notebook. When Excel needs a selected result, publish it once and consume it with BF.OUTPUT().
Publish a completed value
Suppose an upstream cell produces a summary table:
summary = [
["Metric", "Value"],
["Revenue", revenue],
["Gross Margin", gross_margin],
]
Publish it:
publication = bf.publish(
outputs={
"summary": summary,
},
)
publication
Then use the result in Excel:
=BF.OUTPUT("summary")
The value spills into the worksheet when it is a supported array/table shape.
:::important Display the publication widget
Keep the bf.publish() result displayed. Its Anywidget model owns the live output registry used by worksheet formulas.
:::
Publish several results together
Treat bf.publish() as the notebook's explicit public registry:
publication = bf.publish(
outputs={
"summary": summary,
"forecast": forecast,
"exceptions": exceptions,
},
)
publication
Then worksheet consumers can select only what they need:
=BF.OUTPUT("forecast")
=BF.OUTPUT("exceptions")
Keeping the complete registry in one downstream cell makes it easy to inspect what the notebook exposes to Excel.
See which worksheet formulas consume a publication
A displayed publication also tracks active worksheet consumers. Expand the publication widget to see the worksheet reference, published name, and whether the consumer uses an output or function.
If you assigned the publication to a variable, inspect the same mapping from Python:
publication.consumers
publication.consumers["outputs"]
publication.consumers["functions"]
This is useful when changing or removing a published name because it shows which worksheet formulas are currently connected to that registry. In Excel, the references follow live streaming subscriptions and identify the formula anchor cell rather than an entire spill footprint.
Consumer tracking is runtime state. It is not stored as part of the notebook source and should not be treated as a complete static dependency audit for a closed workbook.
What BF.OUTPUT() is for
Use it when a notebook has already performed the expensive or multi-step work and the workbook needs the finished value for:
- a report section;
- a reconciliation table;
- downstream worksheet formulas;
- review or sign-off;
- a dashboard or schedule maintained in Excel.
You do not need to publish every intermediate DataFrame or chart. Keep analysis detail in the notebook unless the workbook actually needs it.
Startup behavior
BF.OUTPUT() is a streaming Excel custom function. When the saved notebook is still starting, Excel can show its normal #BUSY! state. Boardflare waits for the notebook to establish the live publication registry rather than returning a temporary placeholder value.
If #BUSY! never resolves, open Troubleshooting and confirm that the saved notebook starts successfully and the displayed bf.publish() cell runs without error.
Output shapes
Published values are normalized to worksheet matrices:
- a scalar returns one cell;
- a one-dimensional sequence returns one spill row;
- a rectangular two-dimensional sequence returns a spill range;
- supported pandas and NumPy values are converted to worksheet-compatible values.
See Supported values and limits for the detailed conversion boundary.
Save the source that recreates the result
Published Python objects are live-session state. Saving the workbook stores notebook source, not the current in-memory output registry.
When the workbook opens again, Boardflare starts the saved notebook, reruns the reactive analysis, and recreates the publication registry. That is why workbook formulas can remain connected without serializing Python objects into the file.
Create Excel functions
Use BF.FUNCTION() when a calculation should have one centralized Python implementation while workbook users call it from ordinary worksheet cells.
Define the Python function
def discount(price, rate=0.0):
return float(price) * (1 - float(rate))
Publish it explicitly:
publication = bf.publish(
functions={
"discount": discount,
},
)
publication
Then call it from Excel:
=BF.FUNCTION("discount", A1, B1)
The function name is looked up in the explicit mapping passed to bf.publish(). Boardflare does not discover arbitrary notebook globals or use eval.
Publish values and functions together
A normal notebook often exposes both:
publication = bf.publish(
outputs={
"forecast": forecast,
},
functions={
"discount": discount,
"scenario_value": scenario_value,
},
)
publication
This keeps the notebook's Excel-facing contract visible in one place.
Keep expensive work in notebook cells
BF.FUNCTION() is best for relatively short calculations or lookups whose implementation should be centralized in Python.
For expensive modeling, simulation, large transformations, or slow API work:
- perform the expensive work in ordinary reactive notebook cells;
- publish the completed result with
BF.OUTPUT(), or publish a lightweight function that looks up values from the already-computed model.
A long synchronous function can block the notebook kernel until it returns or reaches the worksheet timeout.
Supported function shapes
Common supported signatures include:
def required(a, b):
...
def optional(a, b=10):
...
def variable(a, *items):
...
async def async_lookup(code):
...
Worksheet calls are positional. Required keyword-only parameters and **kwargs are not part of the current worksheet function contract.
Range arguments
Worksheet arguments are converted before Python receives them:
- scalar cell values remain scalars;
- a one-cell range is unwrapped to a scalar;
- a multi-cell range becomes a two-dimensional list;
- omitted trailing optional arguments use Python defaults.
Return values follow the same worksheet conversion families as published outputs. See Supported values and limits.
Why this matters
Without a centralized implementation, a complex calculation can be repeated or re-expressed in many worksheet locations. BF.FUNCTION() lets the implementation live once in the notebook while Excel remains the place where workbook users invoke it.
That is useful for algorithms, valuation logic, scoring, normalization, specialized numerical calculations, and other logic that is awkward to maintain as a long worksheet formula.
Supported values and limits
Use this page when a workbook reference, published result, function call, or notebook save is near a current product boundary.
Workbook inputs
| Workbook reference | Python value |
|---|---|
| One cell | Scalar |
| Multi-cell range | pandas DataFrame |
Multi-cell range with headers=True | DataFrame with first row used as columns |
| Empty workbook cell | May materialize as None |
Current input limits:
| Limit | Value |
|---|---|
| Named inputs | 128 |
| Reference text | 512 characters |
| Cells per input reference | 100,000 |
| Aggregate input cells | 250,000 |
Published outputs
BF.OUTPUT() normalizes supported values to a worksheet matrix:
- scalar → one cell;
- one-dimensional sequence → one spill row;
- rectangular two-dimensional sequence → spill range.
Supported result families include finite numbers, strings, booleans, blanks, supported dates/times, pandas Series/DataFrames, NumPy scalars/arrays, rectangular Python sequences, and supported Excel rich-value dictionaries. A Python None, pandas missing value, or floating-point NaN in a result cell becomes a blank worksheet cell.
Unsupported or unsafe values—including positive/negative infinity, ragged matrices, invalid rich-value payloads, unsupported Python objects, unsafe integers, and oversized results—return deterministic worksheet errors.
| Limit | Value |
|---|---|
| Published outputs | 128 |
| Cells per published value | 100,000 |
Published functions
Worksheet function calls are positional.
Supported callable patterns include required positional arguments, trailing defaults, optional *args, synchronous functions, asynchronous functions, and optional keyword-only parameters that have defaults.
Required keyword-only parameters and **kwargs are not supported by the current worksheet contract.
Argument conversion:
- scalar worksheet values remain scalars;
- a one-cell range becomes a scalar;
- a multi-cell range becomes a two-dimensional list;
- omitted trailing optional arguments use Python defaults.
Current function limits:
| Limit | Value |
|---|---|
| Published functions | 128 |
| Cells per function result | 100,000 |
| Cells across one call's arguments | 100,000 |
| Active worksheet function subscriptions | 512 |
| Concurrent Python function executions | 8 |
| Queued Python function executions | 128 |
| Function timeout | 60 seconds |
Public names
Input, output, and function names are:
- case-sensitive;
- required to start with an ASCII letter;
- limited afterward to ASCII letters, digits, and underscores;
- limited to 128 characters.
Choose names that remain readable in formulas such as:
=BF.OUTPUT("forecast")
Notebook persistence
| Item | Limit |
|---|---|
| Notebook source | 200,000 UTF-8 bytes |
| Complete notebook persistence record | 1,000,000 bytes |
Browser/runtime compatibility
Boardflare's notebook executes in a browser-based Pyodide environment. Pure-Python packages are usually the easiest fit. Packages that depend on unsupported native binaries, unrestricted sockets, desktop APIs, or local file-system behavior may not work even when they work in desktop CPython.
See Packages and environment before designing a workflow around a specialized dependency.
Related reference
- Workbook API for exact API semantics.
- Troubleshooting for common worksheet/runtime symptoms.
- Security and data flow for external network and package boundaries.