Download the markdown file (upload it to your AI at the start of a session)

xlwings Lite: Coding Guidelines for AI Coders (Humans Welcome)

Version 3.2 ยท 20 October 2026. The latest copy is always at https://www.tigzig.com/files/xlwings/AI_CODER_INSTRUCTIONS.md. If your copy shows a lower version number, download the latest. Changes are listed under Version History at the end.

Tested on: xlwings Lite add-in build 1.0.0.104, xlwings 0.37.7, Pyodide 314.0.1 (Python 3.14.2, pandas 3.0.2, numpy 2.4.3), Excel desktop on Windows. Every platform behaviour marked [tested] was run in real Excel on that build. Items marked [docs] come from the official documentation and were not re-run here.

Official docs: https://lite.xlwings.org (machine-readable: https://lite.xlwings.org/llms-full.txt). If something here disagrees with a newer build, trust the build and the docs.


0. THE RULES (check every script against these)

  1. Write DataFrames with .options(index=False). sheet["A1"].value = df writes the pandas index as an extra first column. [tested]
  2. Re-runnable sheets: delete, then add, with a loop. "Name" in book.sheets returns False even for a sheet that exists. Check names with [s.name for s in book.sheets] or loop over the sheets. [tested]
  3. Size new ranges with .resize(), never .expand(). Reads come from a snapshot taken when the script started, so .expand() and used_range cannot see data written in the same run. [tested]
  4. Find tables by name across sheets with the find_table_in_workbook() helper (section 5.2). table.sheet raises AttributeError; use table.parent if you have the table already. [tested]
  5. Table names are unique across the whole workbook. If a user renamed an old output sheet, its table keeps its name, and the next run's tables.add(name=...) fails with a generic InvalidArgument. Omit name= unless the user asked for one, and when you do name a table, remove any existing table of that name first (section 5.3). [tested]
  6. Excel-side errors surface at flush, not at the call. A try/except around sheet.tables.add() catches nothing unless you await book.flush() inside the try. That needs an async def script (section 3). [tested]
  7. Keep text as text. A date-like string ("2026-01-02") becomes a date and "00123" becomes 123. Set number_format = "@" on the target range BEFORE writing (section 4.3). [tested]
  8. np.inf writes as a blank cell, silently. NaN, None and pd.NA also write as blank. Replace infinities before writing if they matter. [tested]
  9. Write formulas through .value: rng.value = "=A2+A3". The .formula setter isn't available yet on this build. [tested]
  10. Formatting you can set: .color, .font.bold/italic/color/size/name, .number_format, Range.autofit(). Not available yet: Sheet.autofit(), column_width, row_height, merge(), wrap_text, and reading back any format (color, number format, font, formula, column width). [tested]
  11. Mark every user-runnable function with @script, with book: xw.Book as its first argument. [docs]
  12. pandas 3 is the default for new workbooks (pandas 2 still runs on older ones). Write code that works on both: no chained inplace=True, no errors="ignore" (section 11). [tested]
  13. Custom functions (@func): take cell inputs as typing.Any and convert inside. A numeric type hint doesn't convert the value: text arrives as text, so x * 2 on "abc" returns "abcabc" with no error. Blank and error cells arrive as None, dates as serial numbers (section 8.2). [tested]
  14. Activate the final output sheet with sheet.activate() so the user sees the result. When the script runs from a sheet button, the add-in jumps back to the button's sheet afterwards (section 3.4). [tested]
  15. Print progress at each major step. print() output appears in the Output pane when the script ends, or right away after await book.flush(). [tested]

1. Where Your Code Runs


2. Scripts

2.1 Basic syntax

import xlwings as xw
from xlwings import script

@script
def hello_world(book: xw.Book):
    sheet = book.sheets[0]
    sheet["A1"].value = "Hello xlwings!"

2.2 Script arguments and button label [docs]

Scripts may take extra arguments after book. In App Mode they render as a form (text box, number box, checkbox, dropdown for Literal, date picker for datetime.date). A script whose extra arguments lack defaults runs only in App Mode.

import datetime as dt
from typing import Annotated, Literal

@script(name="Create Report")          # button label
def create_report(
    book: xw.Book,
    title: str = "Report",
    start: dt.date = dt.date(2026, 1, 1),        # needs xlwings >= 0.36.11
    rows: Annotated[int, {"label": "Number of rows"}] = 10,
    currency: Literal["USD", "EUR"] = "USD",
):
    ...

Arguments must be JSON-serializable. Keyword-only arguments (after *) and **kwargs are not supported.

2.3 Configuration [docs]

@script(
    include=["Sheet1"],               # send only these sheets' content to Python
    exclude=["BigData"],              # or: send everything except these
    button="[mybutton]Sheet1!A1",     # sheet button (section 2.4)
    show_taskpane=True,               # open the task pane on button click
    name="My Button",                 # label in the Run dropdown / App Mode
)

By default the whole workbook's values are sent to Python before the script runs. For big workbooks use include/exclude, or the async API (section 3), to keep it fast.

2.4 Sheet buttons

  1. Insert a shape, give it a name in the Name Box (e.g. xlwings_button).
  2. Right-click > Link > Place in This Document > a cell hidden behind the shape (e.g. B4).
  3. @script(button="[xlwings_button]Sheet1!B4").

How it works: the add-in runs the script when that cell is SELECTED, then selects the cell below. [tested] - Don't leave the linked cell selected when saving. - Scripts that depend on the user's current selection don't work from a button (the click changes the selection). - Because the add-in re-selects the cell under the button after the run, an activate() at the end of a button-run script is undone: Excel returns to the button's sheet. [tested] - Excel on the web can't add hyperlinks to shapes, but buttons set up on desktop work on the web. [docs]


3. The Workbook Engine: Snapshot, Queue, Flush

This is the model behind most "why didn't that work" problems. [tested]

  1. Snapshot at start. Before the script runs, xlwings Lite loads the workbook state. Every sync read (.value, .expand(), used_range, book.sheets) reflects that snapshot, not changes made during the run.
  2. Writes are queued. Assignments are applied to Excel in order when the script ends (or on await book.flush()).
  3. Excel-side errors appear at flush time. An invalid write (overlapping table, duplicate table name, unsupported operation) raises a JsException when the queue is applied, not on the line that queued it. In a plain def script that means the script fails at the end, and the traceback doesn't point at the line that caused it.

The async API gives you control:

@script
async def my_script(book: xw.Book):
    sheet = book.sheets["Data"]
    sheet["A1"].value = "Hello"
    await book.flush()                         # apply queued writes now (and show prints)
    print(await sheet["A1"].get_value())       # read the CURRENT value from Excel -> 'Hello'
    await sheet.load()                         # refresh the snapshot for this sheet
    print(sheet["A1"].expand().address)        # now sees data written this run

Formulas compute in the same run: write a formula, await book.flush(), then await rng.get_value() returns the result. [tested]

Catching an Excel-side error:

@script
async def build(book: xw.Book):
    sheet = book.sheets["Report"]
    try:
        sheet.tables.add(source=sheet["A1"].resize(5, 3))
        await book.flush()                     # the error, if any, is raised HERE
    except Exception as e:
        print(f"Table creation failed: {e}")

4. Writing Data

4.1 DataFrames

sheet["A1"].options(index=False).value = df            # always index=False
df = sheet["A1"].options(pd.DataFrame, index=False, expand="table").value  # read

Writing a DataFrame with mixed-type columns (e.g. "< 600" and 600 in one column), with "" in a float column, or as a 2D Python list ([["Label", 1.6], ["P", 0.8]]) all write correctly on the tested build. Older builds failed on some of these. [tested]

Still good practice, for pandas reasons: use None (not "") for missing numbers so the column stays numeric, and convert genuinely mixed label columns with .astype(str) if you will sort or filter them.

4.2 What values become in Excel [tested]

Python Excel cell
np.nan, None, pd.NA blank
np.inf blank (silent)
np.int64, Int64 number
bool TRUE / FALSE
datetime.date, pd.Timestamp date / date-time
"2026-01-02" (string) converted to a date
"00123" (string) converted to 123
"=A1+A2" (string) formula

4.3 Keeping text as text [tested]

rng = sheet["A2"].resize(len(df), 1)
rng.number_format = "@"                    # format as Text BEFORE writing
rng.options(transpose=True).value = df["ACCOUNT_ID"].tolist()
# Single cells: a leading apostrophe also works: sheet["A1"].value = "'00123"

4.4 Formulas

sheet["C2"].value = "=A2*B2"     # works
# sheet["C2"].formula = "=A2*B2"  # not available yet (raises an error at flush)

5. Sheets and Tables

5.1 Re-runnable output sheets [tested]

def fresh_sheet(book, name):
    for s in book.sheets:
        if s.name == name:
            s.delete()
    return book.sheets.add(name)

# Avoid: this returns False even when the sheet exists
# if "Results" in book.sheets: ...

5.2 Finding a table [tested]

from typing import Any, Optional, Tuple

def find_table_in_workbook(book: xw.Book, table_name: str) -> Tuple[Optional[xw.Sheet], Any]:
    """Search all sheets for a table. Returns (sheet, table) or (None, None)."""
    for sheet in book.sheets:
        if table_name in sheet.tables:          # membership by NAME works for tables
            return sheet, sheet.tables[table_name]
    return None, None

sheet, table = find_table_in_workbook(book, "Portfolio")
df = table.range.options(pd.DataFrame, index=False).value

5.3 Creating tables [tested]

sheet["A1"].options(index=False).value = df
rng = sheet["A1"].resize(df.shape[0] + 1, df.shape[1])   # explicit size, never .expand()
sheet.tables.add(source=rng)                             # let Excel name it (Table1, ...)
def drop_table_everywhere(book, name):
    for s in book.sheets:
        if name in s.tables:
            s.tables[name].delete()

6. Formatting

header = sheet["A1"].resize(1, df.shape[1])
header.color = "#4472C4"            # hex string or RGB tuple (68, 114, 196): both work [tested]
header.font.color = "#FFFFFF"       # always set a contrasting font colour with a fill
header.font.bold = True
sheet["B2"].resize(len(df), 1).number_format = "#,##0.00"
sheet["A1"].resize(df.shape[0] + 1, df.shape[1]).autofit()   # Range.autofit works [tested]

Not available yet on the tested build (each raises JsException: TypeError: globalThis.callbacks[action.func] is not a function at flush): Sheet.autofit(), column_width =, row_height =, merge(), unmerge(), wrap_text =, .formula =, autofill(), Sheet.copy(), Book.save(), app.screen_updating. [tested]

Reading formats back isn't available yet: .color, .number_format, .font.*, .formula, .column_width, .current_region and .table raise NotImplementedError pointing to async getters (await rng.get_color() etc.). On the tested build those getters aren't wired up yet (AttributeError: getRangeData), so keep track of formatting state in Python. [tested]

Presentation conventions: clean column names (df.rename(columns=...)) before writing; formal Excel tables for summaries; a fill always paired with a readable font colour.


7. Pictures and Plots

import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(df["DATE"], df["CLOSE"])
sheet.pictures.add(fig, name="PriceChart", update=True, anchor=sheet["H2"])  # pass the figure directly
plt.close(fig)

8. Custom Functions (@func)

8.1 Basics [docs]

import pandas as pd
from xlwings import func, arg, ret

@func
def hello(name):
    return f"Hello {name}!"            # =HELLO("World") or =HELLO(A1)

@func
@arg("df", pd.DataFrame, index=False)
@ret(index=False)
def describe(df):
    return df.describe().reset_index()

@func
def myfunction(df: pd.DataFrame) -> pd.DataFrame:   # type hints work instead of decorators
    return df

Also available: *args, docstrings and @arg(doc=...) for Excel IntelliSense, typing.Literal parameters (dropdown while typing), @func(name="MyName"), namespaces (__xlwings_func_namespace__), dynamic arrays, volatile functions, object handles, streaming functions, xw.Caller (calling cell, xlwings >= 0.36.17), xw.WithScript() to run a script after the function returns (xlwings >= 0.36.17). See the Custom Functions page of the docs.

8.2 Inputs: what your function actually receives [tested]

Cell content Value received
Number int or float
Text, including numeric text like '7 str, unchanged
Blank cell, ="", or an error cell such as #N/A None
TRUE / FALSE bool
Date serial number (int), unless the argument is hinted dt.datetime
A range, hinted list nested list, blanks as None
from typing import Any

def _to_float(value: Any, default: float = 0.0) -> float:
    if value is None:
        return default
    try:
        return float(value)
    except (ValueError, TypeError):
        return default

@func
def double_it(value: Any):
    return _to_float(value) * 2

8.3 Outputs [docs]

8.4 Debugging #VALUE!

  1. Does the function appear in Excel's autocomplete? If not, main.py (or a module) has an error: check the Problems pane / Output pane.
  2. Use a probe function to see exactly what Python receives:
@func
def debug_input(cell: Any) -> str:
    return f"type={type(cell).__name__} value={cell!r}"

9. Files [docs]


10. Dependencies


11. pandas 3 (default for new workbooks)

pandas 3 uses Copy-on-Write. Two patterns that worked in pandas 2 now fail: [tested]

# SILENTLY DOES NOTHING in pandas 3 (ChainedAssignmentError warning only):
df["col"].fillna(0, inplace=True)
# CORRECT in pandas 2 and 3:
df["col"] = df["col"].fillna(0)

# RAISES ValueError in pandas 3:
pd.to_numeric(s, errors="ignore")
df.apply(pd.to_numeric, errors="ignore")
# CORRECT: coerce, or convert only columns that fully parse
def to_numeric_where_possible(df):
    out = df.copy()
    for c in out.columns:
        conv = pd.to_numeric(out[c], errors="coerce")
        if conv.notna().sum() == out[c].notna().sum():
            out[c] = conv
    return out

12. Web Requests and Databases

12.1 CORS [docs]

Requests run from the user's browser, origin https://addin.xlwings.org (or the self-hosted domain). The server must send Access-Control-Allow-Origin for that origin. GitHub raw files work. If you don't control the server, use a CORS proxy you own. For IP allow-lists, allow the user's IP (requests don't go through xlwings servers).

12.2 Making requests [tested]

from pyodide.http import pyfetch          # built in, no install needed

@script
async def fetch_data(book: xw.Book):
    r = await pyfetch("https://api.example.com/data", headers={"Accept": "application/json"})
    if not r.ok:
        raise RuntimeError(f"API error {r.status}")
    data = await r.json()                 # or: await r.text()

requests (sync) and httpx (async) also work once installed. pd.read_csv("https://raw.githubusercontent.com/...") works for CORS-enabled URLs. [docs] Secrets go in environment variables (section 13), never in code.

Parsing a pipe-delimited text response (pandas 2 and 3):

def parse_pipe_delimited(text: str) -> pd.DataFrame:
    lines = [ln for ln in text.strip().split("\n") if ln.strip()]
    if not lines:
        return pd.DataFrame()
    headers = [h.strip() for h in lines[0].split("|")]
    rows = [[c.strip() or None for c in ln.split("|")] for ln in lines[1:]]  # empty -> missing
    return to_numeric_where_possible(pd.DataFrame(rows, columns=headers))

12.3 Databases [docs]

No TCP sockets, so no direct PostgreSQL/MySQL connections. Options: a web API in front of the database (FastAPI, PostgREST, Supabase, Oracle ORDS, MySQL REST Service, NocoDB), or download a SQLite file and query it with sqlite3.


13. Environment Variables and Security [docs]


These come from building analysis tools in xlwings Lite. They are style and correctness rules, not platform limits.

  1. Separate calculation from Excel I/O. Calculation functions take and return DataFrames, with no xw objects. One final block writes everything. A failure in the maths then never leaves the workbook half-written, and errors are easy to place.
  2. Pivoted report tables: build from a list of dicts. .unstack()/pivot fails or drops columns when a group is missing. Loop over variables and groups, report_rows.append({...}), then pd.DataFrame(report_rows). A plain groupby().agg().reset_index() is fine when nothing is reshaped.
  3. Quantile bins: pd.qcut(..., duplicates="drop"). Without it, a column with few unique values raises ValueError: Bin edges must be unique. [tested]
  4. Convert, then compute statistics, then impute, as separate steps:
df["X"] = pd.to_numeric(df["X"], errors="coerce")
if df["X"].isna().any():
    df["X"] = df["X"].fillna(df["X"].median())
# Avoid: pd.to_numeric(df["X"], errors="coerce").fillna(df["X"].median())
#        -> the median is taken from the ORIGINAL, unconverted column
  1. Balanced samples (test/control): sklearn.model_selection.train_test_split(..., stratify=...).
  2. Narrate progress with print() and finish with a clear success line.
  3. Activate the last output sheet (see rule 14 for the button caveat).

15. Troubleshooting

Symptom Likely cause Fix
JsException: InvalidArgument: The argument is invalid or missing... at the end of a run Duplicate table name (often from a renamed output sheet), or an invalid value in a write drop_table_everywhere() before naming; omit name=; flush after each step to locate it
InvalidArgument: A table can't overlap another table New table over an existing one Delete the old sheet/table first
JsException: TypeError: globalThis.callbacks[action.func] is not a function The operation isn't available yet in Lite (e.g. column_width, merge, .formula =) Leave it out (section 6)
NotImplementedError: Reading ... synchronously isn't supported Reading a format/formula/width Track the state in Python
AttributeError: 'Table' object has no attribute 'sheet' table.sheet find_table_in_workbook() or table.parent
A sheet "doesn't exist" check never fires "Name" in book.sheets returns False Loop over sheet names
Data written this run reads back empty / .expand() returns one cell Snapshot taken at script start .resize(); or await book.flush() then await rng.get_value() / await sheet.load()
Nothing appears in Excel until the script ends Writes are queued await book.flush() (async script)
try/except didn't catch an Excel error Error raised at flush, after the try Flush inside the try
ValueError: invalid error value specified pandas 3 removed errors="ignore" Section 11
A fillna(..., inplace=True) had no effect pandas 3 Copy-on-Write Assign the result back
Leading zeros / IDs / codes changed in Excel Auto-conversion of strings number_format = "@" before writing
ModuleNotFoundError Package not in requirements.txt Add it and restart
CORS error on a request Server doesn't allow https://addin.xlwings.org Add the header, or use your own CORS proxy
Custom function shows #VALUE! A None (blank cell) reached arithmetic, or the module has an error Section 8.2 and 8.4
Custom function returns a strange repeated string like abcabc Text was multiplied: type hints don't convert Section 8.2

16. Reference Script Pattern

import pandas as pd
import xlwings as xw
from xlwings import script


@script
async def summarise_portfolio(book: xw.Book):
    print("Loading data...")
    src_sheet, table = find_table_in_workbook(book, "Portfolio")
    if table is None:
        raise ValueError("Table 'Portfolio' not found. Create it via Insert > Table.")
    df = table.range.options(pd.DataFrame, index=False).value

    # PHASE 1: calculation, no Excel objects
    df["Value"] = pd.to_numeric(df["Value"], errors="coerce")
    summary = (
        df.groupby("Category", as_index=False)
        .agg(Count=("Value", "size"), Total=("Value", "sum"))
        .rename(columns={"Category": "Asset Class"})
    )
    print(f"Calculated {len(summary)} rows.")

    # PHASE 2: write
    out = fresh_sheet(book, "Summary")
    out["A1"].options(index=False).value = summary
    rng = out["A1"].resize(summary.shape[0] + 1, summary.shape[1])
    header = out["A1"].resize(1, summary.shape[1])
    header.color = "#4472C4"
    header.font.color = "#FFFFFF"
    header.font.bold = True
    try:
        out.tables.add(source=rng)
        await book.flush()                 # surface any Excel-side error here
    except Exception as e:
        print(f"Table creation failed: {e}")
    rng.autofit()
    out.activate()
    print("Done: results on sheet 'Summary'.")

(find_table_in_workbook from section 5.2, fresh_sheet from section 5.1.)


17. For AI Coding Agents: Run and Test Code in Real Excel Yourself

If you are an AI coding agent working on the user's Windows machine (Claude Code, Cursor, Antigravity, Codex or similar), you can run your xlwings Lite code inside the user's Excel and read the output and traceback back yourself. The user stops copying code in and errors out. Everything in this file marked [tested] was verified this way.

Requirements: Excel desktop on Windows (mounted Local Folders are Windows-only), Python on the same machine with pywin32 (pip install pywin32), and about 10 minutes of one-time setup by the user.

17.1 How it works

  1. A dedicated folder on disk (the "lab folder") is mounted in xlwings Lite at /xlw_lab.
  2. The workbook's main.py holds a small runner script, pasted once. It executes /xlw_lab/task.py, captures stdout and the traceback, and writes /xlw_lab/result.json.
  3. The runner is bound to a sheet button. A sheet button fires when its linked cell is selected, so your trigger script selects that cell over COM. No click is needed.
  4. You write task.py, trigger, and read result.json. To check what landed in Excel independently of xlwings, read cells over COM.

17.2 One-time setup (the user does this)

  1. Create the lab folder, e.g. C:\xlw_lab, containing only lab files. Code run from the workbook can read and write everything in it.
  2. Create lab.xlsx with a sheet named Runner. Insert a shape over cell B4, name it run_btn in the Name Box, and link it (right-click > Link > Place in This Document) to Runner!B4.
  3. Open xlwings Lite in that workbook. Replace all of main.py with the runner in 17.3.
  4. Menu > Files > Local Folders > Add Folder > the lab folder. Accept both prompts with "Allow on every visit". Set the mount path to /xlw_lab with the pencil icon. Paths are case-sensitive.
  5. Settings > Workbook > autostart on. Save the workbook and keep it open while the agent works.

17.3 The runner (main.py)

import ast, contextlib, datetime, inspect, io, json, sys, traceback
import xlwings as xw
from xlwings import script

LAB = "/xlw_lab"


def _purge_lab_modules():
    # Re-import helper modules from the lab folder fresh on every run.
    for name, mod in list(sys.modules.items()):
        if (getattr(mod, "__file__", None) or "").startswith(LAB):
            del sys.modules[name]


@script(button="[run_btn]Runner!B4")
async def run_task(book: xw.Book):
    try:
        run_id = open(f"{LAB}/run_id.txt", encoding="utf-8").read().strip()
    except Exception:
        run_id = "none"
    out, status, tb = io.StringIO(), "OK", ""
    if LAB not in sys.path:
        sys.path.insert(0, LAB)
    _purge_lab_modules()
    try:
        src = open(f"{LAB}/task.py", encoding="utf-8").read()
        code = compile(src, "task.py", "exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
        with contextlib.redirect_stdout(out), contextlib.redirect_stderr(out):
            res = eval(code, {"book": book, "xw": xw, "__name__": "__task__"})
            if inspect.iscoroutine(res):
                await res
            await book.flush()  # surface Excel-side errors inside the try
    except BaseException:
        status, tb = "ERROR", traceback.format_exc()
    result = {"run_id": run_id, "status": status, "xlwings": xw.__version__,
              "python": sys.version.split()[0], "stdout": out.getvalue(), "traceback": tb,
              "ended": datetime.datetime.now().isoformat(timespec="seconds")}
    with open(f"{LAB}/result.json", "w", encoding="utf-8") as f:
        json.dump(result, f, indent=2)
    book.sheets["Runner"]["B2"].value = f"{run_id}  {status}  {result['ended']}"

task.py gets book and xw and may use top-level await (e.g. await book.flush()).

17.4 The trigger (runs on the machine, outside Excel)

# run_task.py  -  usage: python run_task.py path/to/task.py
import json, secrets, shutil, sys, time
from pathlib import Path
import win32com.client

LAB = Path(r"C:\xlw_lab")
WORKBOOK = "lab.xlsx"

shutil.copyfile(sys.argv[1], LAB / "task.py")
run_id = time.strftime("%H%M%S") + "-" + secrets.token_hex(3)
(LAB / "run_id.txt").write_text(run_id, encoding="utf-8")

xl = win32com.client.GetActiveObject("Excel.Application")
wb = next(b for b in xl.Workbooks if b.Name.lower() == WORKBOOK)
wb.Activate()
ws = wb.Worksheets("Runner")
ws.Activate()
ws.Range("B6").Select()   # make sure B4 isn't already selected
ws.Range("B4").Select()   # fires the button handler

t0 = time.time()
while time.time() - t0 < 180:
    try:
        res = json.loads((LAB / "result.json").read_text(encoding="utf-8"))
    except Exception:
        res = None
    if res and res.get("run_id") == run_id:
        print(res["status"], res["stdout"], res["traceback"], sep="\n")
        sys.exit(0 if res["status"] == "OK" else 1)
    time.sleep(0.5)
sys.exit("timeout: is the workbook open with xlwings Lite running?")

The run id makes sure you never read a stale result. A run usually returns in 1 to 3 seconds.

17.5 Testing custom functions

  1. The user also mounts a folder as /personal (17.2 step 4). Put the functions under test in personal/projects/<name>/, where <name> is part of the workbook's filename (lab for lab.xlsx), so they load only into that workbook.
  2. After changing function code, restart the add-in from a task: import js; js.window.location.reload(). Wait about 20 seconds; the runner and the new functions are then live.
  3. Write formulas and read results over COM: ws.Range("C2").Formula = "=MY_FUNC(B2)", wait while .Text shows #BUSY!, then read .Text.

17.6 Notes


Version History