Excel Formulas skill for Codex

Formulas that survive missing keys and new rows. Codex picks the right lookup or aggregation for the user's Excel version, writes formulas that handle missing keys, text numbers and duplicates on purpose, and explains each argument. When a formula breaks it traces the error with a cause table instead of wrapping it in IFERROR, and when formulas are written from Python it adds the _xlfn and _xlpm prefixes Excel needs.

  • Documents & Office
  • $excel-formulas
  • 167 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- excel-formulas

Installs to ~/.agents/skills/excel-formulas/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $excel-formulas, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes, explains and debugs Excel formulas - XLOOKUP and INDEX/XMATCH lookups, SUMIFS-style aggregation, dynamic arrays (FILTER, SORT, UNIQUE, spill ranges), LET and LAMBDA, and error tracing for #N/A, #VALUE!, #SPILL! and #NAME?. Also covers writing modern formulas from code with the _xlfn prefixes Excel requires. Use when the user asks for a formula, pastes one that returns an error or wrong result, wants a slow workbook sped up, or needs formulas generated by openpyxl or XlsxWriter to work.

Use it when

  • You need a lookup, conditional sum or dynamic array formula
  • A formula returns an error or a wrong number
  • A workbook recalculates slowly because of volatile or array formulas
  • Formulas written by openpyxl or XlsxWriter fail when opened in Excel

Use something else when

How to check the result

Test the formula against a normal key, a missing key, a blank, a duplicate and the first and last rows, and cross-check one aggregate with an independent method.

The mistake it prevents

Wrapping a lookup in IFERROR(..., 0) turns a broken reference or typo into a believable zero that nobody notices.

~/.agents/skills/excel-formulas/SKILL.md

--- name: excel-formulas description: Writes, explains and debugs Excel formulas - XLOOKUP and INDEX/XMATCH lookups, SUMIFS-style aggregation, dynamic arrays (FILTER, SORT, UNIQUE, spill… license: MIT ---

A good formula returns the right answer for the edge cases (missing key, blank, text number, duplicate), survives inserted rows and columns, is readable by the next analyst, and fails loudly where a silent wrong number would be worse than an error.

Workflow

  1. Ask (or infer from the file) which Excel the audience runs: Microsoft 365 / Excel 2021+ has XLOOKUP, dynamic arrays and LET; Excel 2016/2019 does not. LAMBDA and its helpers need 365 or 2024. Google Sheets supports XLOOKUP, FILTER, LET and LAMBDA.
  2. Look at the data shape first: header row, key uniqueness, data types (numbers stored as text), whether the source is an Excel Table (prefer structured references if so).
  3. Write the formula, then test it against: a normal key, a missing key, a blank, a duplicate key, and the first and last rows.
  4. Explain it in one or two sentences, naming each argument’s role.

Pick the function

NeedUseAvoid
Exact lookup by keyXLOOKUP(key, keys, results, "Not found")VLOOKUP(..., TRUE) default approximate match
Lookup on 365-less ExcelINDEX(results, MATCH(key, keys, 0))VLOOKUP with a hard-coded column number
Two-way (row and column)INDEX(grid, XMATCH(r, rows), XMATCH(c, cols))Nested IFs
Bracket / tier lookupXLOOKUP(x, lower_bounds, rates, , -1) (next smaller)Long nested IF chains
Conditional sum / count / averageSUMIFS, COUNTIFS, AVERAGEIFSSUMPRODUCT unless criteria need math
Return all matching rowsFILTER(range, condition, "None")Helper columns with SMALL(IF(...))
Distinct list, sortedSORT(UNIQUE(range))Remove Duplicates (not live)
Reuse an intermediate valueLET(name, expr, ...)Repeating the same sub-expression
Reusable custom functionNamed LAMBDA in Name ManagerVBA UDF (blocked in many orgs)
Text splitting / joiningTEXTSPLIT, TEXTJOIN, TEXTBEFORE, TEXTAFTERMID(FIND(...)) stacks

Patterns

// Lookup with a fallback and exact match (default match_mode 0)
=XLOOKUP([@CustomerID], Customers[ID], Customers[Region], "Unknown")

// Multiple criteria: boolean arrays multiply to 1 only where all are true
=XLOOKUP(1, (Orders[Region]=G2) * (Orders[Month]=H2), Orders[Total], 0)

// Last match instead of first (search_mode -1)
=XLOOKUP(G2, Log[User], Log[Timestamp], , 0, -1)

// Commission tier: thresholds sorted ascending, take the next smaller match
=XLOOKUP(B2, Tiers[From], Tiers[Rate], 0, -1)

// Top 5 orders for a region, sorted by total descending, spilled
=TAKE(SORT(FILTER(Orders, Orders[Region]=G2, "No orders"), 4, -1), 5)

// Readable multi-step calculation
=LET(
   net,   SUMIFS(Sales[Net], Sales[Month], G2),
   cost,  SUMIFS(Costs[Amount], Costs[Month], G2),
   IF(net = 0, "", (net - cost) / net)
)

// Named function: Name Manager > New > Name: GROSSUP
=LAMBDA(net, rate, ROUND(net * (1 + rate), 2))
// then in cells: =GROSSUP(B2, Inputs!$B$1)

// Row-wise logic over a spill without helper columns
=BYROW(B2:D20, LAMBDA(r, MAX(r) - MIN(r)))

Spill rules: refer to a whole spilled result with # (=SUM(F2#)); a spill needs empty cells below/right, otherwise #SPILL!; spills cannot go inside an Excel Table.

Debugging

ErrorUsual causeFirst check
#N/AKey not found: trailing spaces, number vs text, wrong range=ISNUMBER(key) vs =ISNUMBER(first key); =LEN(key)
#VALUE!Text in arithmetic, wrong argument type, mismatched array sizesEvaluate Formula; check ISTEXT on inputs
#REF!Deleted row/column or sheet the formula pointed atTrace Precedents; restore from version history
#NAME?Misspelled function, missing quotes, function newer than this Excel, or _xlfn missing when written by codeRe-type the function name; check Excel version
#SPILL!Something occupies the spill range, or spilling inside a TableClick the error badge; clear the range
#DIV/0!Empty or zero denominatorDecide: blank (IF(d=0,"",n/d)) or keep the error
#CALC!FILTER with no matches and no if_empty, or empty arrayAdd the third argument
Wrong number, no errorNumbers stored as text ignored by SUMIFS, duplicate keys, absolute/relative refs shifted on copy=SUMPRODUCT(--ISTEXT(range)); COUNTIF(keys, key)>1

Technique, in order:

  1. Formulas > Evaluate Formula, step through until the first unexpected intermediate.
  2. Select part of the formula in the bar and press F9 to see that piece’s value (then Esc, never Enter, or the result replaces the expression).
  3. Check calculation mode (Formulas > Calculation Options > Automatic); manual mode makes correct formulas show stale values.
  4. Clean inputs at the source rather than wrapping lookups: TRIM, CLEAN, SUBSTITUTE(x, CHAR(160), "") for non-breaking spaces from web exports, VALUE or Data > Text to Columns for text numbers.

Use IFERROR only around the specific expected failure and return something deliberate. A blanket IFERROR(..., 0) turns a broken reference into a plausible zero. Prefer IFNA for lookups so other errors still surface.

Performance

  • Volatile functions (OFFSET, INDIRECT, TODAY, NOW, RAND, CELL, INFO) recalculate on every change. Replace OFFSET dynamic ranges with Tables or INDEX(A:A,1):INDEX(A:A,n).
  • Whole-column references (A:A) are fine for SUMIFS/XLOOKUP but slow in array math ((A:A=x)*(B:B=y)); use Table columns or bounded ranges.
  • Compute a repeated lookup once in LET or a helper column instead of in every cell.
  • Thousands of identical SUMIFS over a large table: consider a PivotTable or GROUPBY/PIVOTBY (Microsoft 365) instead.

Writing formulas from code

Files store formulas in an older, English, comma-separated syntax. Functions added after Excel 2010 must carry a prefix or Excel shows #NAME?:

FunctionStored as
XLOOKUP, XMATCH, UNIQUE, SEQUENCE, IFS, MAXIFS, MINIFS, TEXTJOIN, CONCAT, SWITCH_xlfn.NAME(
FILTER, SORT, SORTBY_xlfn._xlws.NAME(
LET, LAMBDA_xlfn.LET( / _xlfn.LAMBDA(, with each parameter as _xlpm.name

XlsxWriter adds the prefixes and marks dynamic arrays for you:

import xlsxwriter

wb = xlsxwriter.Workbook("report.xlsx", {"use_future_functions": True})
ws = wb.add_worksheet("Report")
ws.write_dynamic_array_formula("E2", '=SORT(UNIQUE(A2:A500))')
ws.write_formula("F2", '=XLOOKUP(E2, A2:A500, C2:C500, "n/a")')
ws.write_formula("G2", "=LET(_xlpm.t, SUM(C2:C500), _xlpm.t/COUNT(C2:C500))")
wb.close()

openpyxl writes the formula string verbatim: add prefixes yourself (ws["F2"] = '=_xlfn.XLOOKUP(E2,A2:A500,C2:C500,"n/a")'). openpyxl cannot mark a formula as a dynamic array, so spilling functions written with it open as =@FILTER(...) and return one value. Use XlsxWriter for new files with spills, or have the formula return a single value.

Also from code: use commas as argument separators and . decimals even when the user’s Excel is set to a locale with semicolons; quote sheet names with spaces ('Q3 Data'!A1); make column references absolute where the formula will be copied ($B$1).

For the workbook mechanics (formatting, tables, saving, recalculation) see $xlsx-spreadsheets.

Verification

  • Build a small test block next to the formula (or in a scratch sheet) with the edge cases listed in the workflow and their expected results; confirm each.
  • For generated files, open in Excel or recalculate and scan for errors with the audit in $xlsx-spreadsheets; LibreOffice 24.8+ evaluates XLOOKUP, FILTER, SORT, UNIQUE and LET, older versions show errors for them, so do not trust an old converter’s results.
  • Cross-check one aggregate with an independent method (pivot, Python, calculator).

Answer format

**Formula (F2):** `=XLOOKUP(1, (Orders[Region]=G2)*(Orders[Month]=H2), Orders[Total], 0)`
**How it works:** builds a 1/0 array that is 1 only where region and month both match,
then returns the Total from the first such row; 0 if none match.
**Needs:** Excel 365 or 2021+. For Excel 2019 use
`=IFERROR(INDEX(Orders[Total], MATCH(1, (Orders[Region]=G2)*(Orders[Month]=H2), 0)), 0)`.
**Tested:** match, no match (0), duplicate rows (first wins - confirm that is intended).

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Excel Formulas"
  short_description: "Formulas that survive missing keys and new rows"
  default_prompt: "$excel-formulas Write a formula that pulls the price for a SKU and region from the Prices table"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem