spreadsheet-modeling
Use this skill when building, auditing, or optimizing spreadsheet models in Excel or Google Sheets. Triggers on formula writing, pivot table creation, dashboard design, data validation, conditional formatting, macro/VBA scripting, Apps Script automation, financial modeling, what-if analysis, XLOOKUP/INDEX-MATCH lookups, array formulas, and workbook architecture. Covers advanced Excel and Google Sheets for analysts, finance professionals, and operations teams.
data excelgoogle-sheetsformulaspivot-tablesdashboardsmacrosWhat is spreadsheet-modeling?
Use this skill when building, auditing, or optimizing spreadsheet models in Excel or Google Sheets. Triggers on formula writing, pivot table creation, dashboard design, data validation, conditional formatting, macro/VBA scripting, Apps Script automation, financial modeling, what-if analysis, XLOOKUP/INDEX-MATCH lookups, array formulas, and workbook architecture. Covers advanced Excel and Google Sheets for analysts, finance professionals, and operations teams.
spreadsheet-modeling
spreadsheet-modeling is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex, and 1 more. Building, auditing, or optimizing spreadsheet models in Excel or Google Sheets.
Quick Facts
| Field | Value |
|---|---|
| Category | data |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex, mcp |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill spreadsheet-modeling- The spreadsheet-modeling skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
Spreadsheet modeling is the discipline of building structured, auditable, and maintainable workbooks in Microsoft Excel or Google Sheets. A well-built model separates inputs from calculations from outputs, uses named ranges for clarity, and avoids hardcoded values buried in formulas. This skill equips an agent to write advanced formulas, design pivot tables and dashboards, author VBA macros and Apps Script automations, and architect workbooks that scale from quick analyses to enterprise financial models.
Tags
excel google-sheets formulas pivot-tables dashboards macros
Platforms
- claude-code
- gemini-cli
- openai-codex
- mcp
Related Skills
Pair spreadsheet-modeling with these complementary skills:
Frequently Asked Questions
What is spreadsheet-modeling?
Use this skill when building, auditing, or optimizing spreadsheet models in Excel or Google Sheets. Triggers on formula writing, pivot table creation, dashboard design, data validation, conditional formatting, macro/VBA scripting, Apps Script automation, financial modeling, what-if analysis, XLOOKUP/INDEX-MATCH lookups, array formulas, and workbook architecture. Covers advanced Excel and Google Sheets for analysts, finance professionals, and operations teams.
How do I install spreadsheet-modeling?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill spreadsheet-modeling in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support spreadsheet-modeling?
This skill works with claude-code, gemini-cli, openai-codex, mcp. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
Spreadsheet Modeling
Spreadsheet modeling is the discipline of building structured, auditable, and maintainable workbooks in Microsoft Excel or Google Sheets. A well-built model separates inputs from calculations from outputs, uses named ranges for clarity, and avoids hardcoded values buried in formulas. This skill equips an agent to write advanced formulas, design pivot tables and dashboards, author VBA macros and Apps Script automations, and architect workbooks that scale from quick analyses to enterprise financial models.
When to use this skill
Trigger this skill when the user:
- Needs help writing or debugging a spreadsheet formula (XLOOKUP, INDEX-MATCH, SUMIFS, array formulas, etc.)
- Wants to build or modify a pivot table or pivot chart
- Asks to create a dashboard with charts, KPIs, or conditional formatting
- Needs a VBA macro or Google Apps Script to automate a spreadsheet task
- Wants to build a financial model, forecast, or what-if scenario analysis
- Asks about data validation rules, drop-downs, or input constraints
- Needs to clean, transform, or restructure data within a spreadsheet
- Wants to optimize a slow workbook or audit an existing model for errors
Do NOT trigger this skill for:
- Database queries or SQL - use a database skill instead
- Python/R data analysis (pandas, NumPy) - use a data-engineering skill instead
Key principles
Separate inputs, calculations, and outputs - Every model should have a clear flow: assumptions/inputs on one sheet, calculations on another, and summary/output on a third. Never mix hardcoded inputs into formula cells.
One formula per row/column pattern - A column of formulas should use the same formula copied down. If row 5 has a different formula than row 6 in the same column, the model is fragile and hard to audit.
Name things - Use named ranges and structured table references instead of raw cell addresses.
=Revenue * Tax_Rateis auditable;=B7*$K$2is not.No magic numbers - Every literal value in a formula should either be a named constant or live in a clearly labeled input cell. If you see
*1.08in a formula, extractTax_Rateas a named input.Design for the next person - Use consistent formatting, color-code input cells (typically blue font on yellow background), and add cell comments for non-obvious logic. Models outlive their creators.
Core concepts
Workbook architecture organizes a model into layers. The standard pattern is: Inputs/Assumptions sheet (all editable parameters), Calculations sheet (pure formulas referencing inputs), and Output/Dashboard sheet (charts, KPIs, summary tables). Larger models add a Cover/TOC sheet and a Data sheet for raw imports.
Structured tables (Excel Tables / named ranges in Sheets) are the foundation
of maintainable formulas. A table auto-expands when data is added, supports
structured references like =SUM(Sales[Revenue]), and makes pivot tables
reliable. Always convert raw data ranges to tables before building on them.
Array formulas and dynamic arrays enable powerful multi-cell calculations. Excel's FILTER, SORT, UNIQUE, and SEQUENCE functions (and their Google Sheets equivalents) replace many complex INDEX-MATCH or helper-column patterns with single formulas that spill results across multiple cells.
Pivot tables summarize large datasets without formulas. They support grouping, calculated fields, slicers for interactivity, and can feed charts. The key skill is choosing the right row/column/value/filter field layout for the question being asked.
Common tasks
Write a lookup formula
Use XLOOKUP (Excel 365+) or INDEX-MATCH as the universal lookup pattern. Avoid VLOOKUP for new work - it breaks when columns are inserted.
XLOOKUP (Excel 365+ / Google Sheets):
=XLOOKUP(lookup_value, lookup_array, return_array, "Not found", 0)INDEX-MATCH (all versions):
=INDEX(return_range, MATCH(lookup_value, lookup_range, 0))Two-criteria lookup (INDEX-MATCH-MATCH):
=INDEX(data_range, MATCH(row_value, row_headers, 0), MATCH(col_value, col_headers, 0))Always wrap lookups in IFERROR or use XLOOKUP's built-in if_not_found argument to handle missing values gracefully.
Build a conditional aggregation
Use SUMIFS/COUNTIFS/AVERAGEIFS for multi-criteria aggregation.
=SUMIFS(Sales[Amount], Sales[Region], "West", Sales[Date], ">="&DATE(2025,1,1))Dynamic array alternative (Excel 365+):
=SUM(FILTER(Sales[Amount], (Sales[Region]="West") * (Sales[Date]>=DATE(2025,1,1))))SUMIFS criteria ranges must all be the same size. Mismatched ranges produce a #VALUE! error with no helpful message.
Create a pivot table
Step-by-step framework for designing a pivot table:
- Define the question - "What is total revenue by region and product category for Q1?"
- Identify the fields - Rows: Region, Product Category. Values: SUM of Revenue. Filter: Date (Q1)
- Build the pivot - Select data table, Insert > PivotTable, drag fields to areas
- Format - Apply number formatting to values, add a slicer for Date for interactivity
- Refresh strategy - If source data changes, right-click > Refresh. For auto-refresh, use VBA or Apps Script
Calculated field example (add a margin calculation inside the pivot):
Margin = Revenue - CostPivot tables silently exclude rows with blank values in row/column fields. Clean your data before pivoting.
Design a dashboard
Build dashboards on a dedicated output sheet that references calculation sheets.
Layout checklist:
- Top row: Title, date range selector (data validation drop-down), refresh button
- Row 2-4: KPI cards (large numbers) - Revenue, Growth %, Units Sold
- Main area: 2-3 charts (combo chart for trends, bar chart for comparisons, pie only if fewer than 6 categories)
- Bottom or right: Detail table with conditional formatting (data bars, color scales)
KPI formula pattern:
=TEXT(total_revenue, "$#,##0") & " (" & TEXT(growth_rate, "+0.0%;-0.0%") & ")"Conditional formatting rules for a heatmap:
- Select the data range
- Apply Color Scale: Green (high) to Red (low) for positive metrics
- Apply Data Bars for volume metrics
- Use Icon Sets (arrows) for period-over-period change columns
Write a VBA macro (Excel)
Use VBA for repetitive tasks, custom functions, or workbook automation.
Basic macro structure:
Sub FormatReport()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
ws.Range("A1:Z1").Font.Bold = True
ws.UsedRange.Columns.AutoFit
ws.Range("D2:D" & lastRow).NumberFormat = "$#,##0.00"
MsgBox "Report formatted: " & lastRow - 1 & " rows processed."
End SubCustom function (UDF):
Function WeightedAverage(values As Range, weights As Range) As Double
Dim i As Long
Dim sumProduct As Double
Dim sumWeights As Double
For i = 1 To values.Cells.Count
sumProduct = sumProduct + values.Cells(i).Value * weights.Cells(i).Value
sumWeights = sumWeights + weights.Cells(i).Value
Next i
If sumWeights = 0 Then
WeightedAverage = 0
Else
WeightedAverage = sumProduct / sumWeights
End If
End FunctionVBA macros must be saved in .xlsm format. UDFs are volatile by default in some contexts - avoid calling volatile functions inside them.
Write a Google Apps Script
Use Apps Script for automation in Google Sheets (email alerts, data imports, scheduled tasks).
function sendWeeklyReport() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const dashboard = ss.getSheetByName("Dashboard");
const revenue = dashboard.getRange("B2").getValue();
const growth = dashboard.getRange("B3").getValue();
const subject = "Weekly Report - Revenue: $" + revenue.toLocaleString();
const body = [
"Weekly KPIs:",
"Revenue: $" + revenue.toLocaleString(),
"Growth: " + (growth * 100).toFixed(1) + "%",
"",
"View full dashboard: " + ss.getUrl()
].join("\n");
MailApp.sendEmail("team@company.com", subject, body);
}
function createTrigger() {
ScriptApp.newTrigger("sendWeeklyReport")
.timeBased()
.everyWeeks(1)
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(9)
.create();
}Apps Script has a 6-minute execution limit. For large datasets, use batch processing with continuation tokens.
Build a scenario / what-if analysis
Use Data Tables (Excel) or manual scenario switching for sensitivity analysis.
Two-variable data table pattern:
- Place the output formula in the top-left corner of the table
- Row input values across the top (e.g., price points)
- Column input values down the left (e.g., volume levels)
- Select the entire table, Data > What-If Analysis > Data Table
- Set row input cell and column input cell references
Scenario Manager alternative:
=CHOOSE(Scenario_Selector, base_value, optimistic_value, pessimistic_value)Where Scenario_Selector is a data-validation drop-down cell containing 1, 2, or 3.
Data Tables recalculate every time the workbook recalculates. In large models, set calculation to Manual (Ctrl+Shift+F9 to force recalc) to avoid slowdowns.
Gotchas
Pivot tables silently exclude blank rows - If any row in your source data has a blank value in the row or column field, that row is excluded from the pivot entirely with no warning. Clean blank values (replace with "Unknown" or 0) before building pivots that need complete coverage.
SUMIFS range size mismatch produces #VALUE! with no useful message - All criteria ranges in a SUMIFS must be the exact same dimensions as the sum range. A single range that is one row taller than the others throws #VALUE! with no indication of which range is mismatched. Build a helper formula to check range sizes when debugging.
Data Tables recalculate on every edit in large models - Excel recalculates all Data Tables whenever any cell in the workbook changes. In models with large Data Tables, this can make every keystroke take seconds. Set calculation mode to Manual (Formulas > Calculation Options > Manual) and use Ctrl+Alt+F9 to force recalc when needed.
OFFSETandINDIRECTbreak when used in table references - Both functions are volatile and recalculate on every change. Using them inside structured table references (Table[Column]) can cause unexpected reference errors when tables are resized. PreferINDEXas a non-volatile alternative toOFFSET.Apps Script 6-minute execution limit fails silently on large datasets - A script that times out after 6 minutes does not throw an error to the user - it just stops partway through the operation, leaving data in a partially modified state. For large datasets, implement batch processing with
PropertiesServiceto store a continuation token and re-trigger the script.
Anti-patterns / common mistakes
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
| Hardcoded numbers in formulas | =B5*1.08 is unauditable - no one knows what 1.08 means in 6 months |
Extract to a named input cell: =B5*Tax_Rate |
| Merging cells | Breaks sorting, filtering, formulas, and pivot table source ranges | Use "Center Across Selection" formatting or adjust column widths instead |
| One giant sheet | Mixing inputs, calculations, and outputs on one sheet makes auditing impossible | Separate into Input, Calc, and Output sheets with a clear flow |
| Circular references | Intentional circulars (iterative calc) are fragile and confuse other users | Restructure the logic to avoid circulars, or document heavily if truly required |
| VLOOKUP with column index | =VLOOKUP(A1,data,3,FALSE) breaks when columns are inserted |
Use XLOOKUP or INDEX-MATCH which reference the return column directly |
| No error handling in formulas | #N/A and #DIV/0! errors cascade through dependent cells and break dashboards | Wrap in IFERROR or IFNA with meaningful defaults |
| Volatile functions everywhere | NOW(), INDIRECT(), OFFSET() recalculate on every edit, slowing the workbook | Use non-volatile alternatives (INDEX instead of OFFSET, static timestamps via VBA) |
References
For detailed content on specific sub-domains, read the relevant file from references/:
references/formula-patterns.md- Advanced formula cookbook: array formulas, LAMBDA, LET, dynamic arrays, regexreferences/vba-patterns.md- VBA and Apps Script patterns: loops, error handling, UserForms, API callsreferences/financial-modeling.md- Financial model architecture: DCF, three-statement models, sensitivity tables
Only load a references file if the current task requires deep detail on that topic.
References
financial-modeling.md
Financial Modeling Patterns
Model architecture
Every financial model follows the same three-layer architecture:
[Assumptions Sheet] --> [Calculations Sheet] --> [Output Sheet]
(inputs) (formulas only) (dashboard/summary)Assumptions sheet
Contains every editable input in the model. Organize into sections:
| Section | Examples |
|---|---|
| Revenue drivers | Units sold, price per unit, growth rate |
| Cost drivers | COGS %, headcount, salary, rent |
| Capital | CapEx schedule, depreciation method, useful life |
| Financing | Debt amount, interest rate, repayment schedule |
| Tax | Tax rate, NOL carryforward |
| Timing | Forecast start, number of periods, fiscal year end |
Formatting convention:
- Blue font = hardcoded input (editable)
- Black font = formula (do not edit)
- Yellow background = key assumption that drives the model
Calculations sheet
Pure formulas. No hardcoded values. Every cell references either the Assumptions sheet or other cells on the Calculations sheet.
Output sheet
Summary tables, charts, and KPIs. Typically contains:
- Income statement summary
- Key metrics (revenue growth, margins, EBITDA)
- Scenario comparison table
- Valuation summary
Three-statement model
Links the Income Statement, Balance Sheet, and Cash Flow Statement.
Income Statement structure
Revenue
- COGS
= Gross Profit
- Operating Expenses (SG&A, R&D)
= EBITDA
- Depreciation & Amortization
= EBIT (Operating Income)
- Interest Expense
= EBT (Earnings Before Tax)
- Taxes
= Net IncomeFormula pattern for revenue line:
=Prior_Period_Revenue * (1 + Revenue_Growth_Rate)Formula pattern for expense lines:
=Revenue * Expense_As_Pct_Of_RevenueBalance Sheet structure
Assets:
Cash
Accounts Receivable
Inventory
PP&E (net of depreciation)
Other Assets
Liabilities:
Accounts Payable
Short-term Debt
Long-term Debt
Equity:
Common Stock
Retained Earnings (prior + Net Income - Dividends)Balance check formula (must always equal zero):
=Total_Assets - Total_Liabilities - Total_EquityIf the balance check is not zero, there is an error in the model. Use conditional formatting to turn the check cell red if non-zero.
Cash Flow Statement structure
Operating Activities:
Net Income
+ Depreciation & Amortization
- Change in Working Capital
(Change in AR, Inventory, AP)
Investing Activities:
- Capital Expenditures
+ Asset Sales
Financing Activities:
+ Debt Issuance
- Debt Repayment
- Dividends
+ Equity Issuance
= Net Change in Cash
+ Beginning Cash
= Ending Cash (must tie to Balance Sheet)Working capital change formula:
=-(Current_AR - Prior_AR) - (Current_Inventory - Prior_Inventory) + (Current_AP - Prior_AP)DCF valuation
Free Cash Flow (FCF) projection
EBIT
* (1 - Tax Rate)
= NOPAT
+ Depreciation & Amortization
- Capital Expenditures
- Change in Working Capital
= Free Cash FlowTerminal value (perpetuity growth method)
=FCF_Last_Year * (1 + Terminal_Growth_Rate) / (WACC - Terminal_Growth_Rate)
Discount FCF to present value
=FCF_Year_N / (1 + WACC) ^ NFull DCF formula in a row:
Year: 1 2 3 4 5 Terminal
FCF: =calc =calc =calc =calc =calc
Terminal Value: =TV formula
Discount Factor: =1/(1+WACC)^1 ...
PV of FCF: =FCF*DF =FCF*DF ...Enterprise Value = SUM(PV of FCFs) + PV of Terminal Value
Equity Value = Enterprise Value - Net Debt
Value per Share = Equity Value / Shares OutstandingWACC calculation
=Equity_Weight * Cost_Of_Equity + Debt_Weight * Cost_Of_Debt * (1 - Tax_Rate)Cost of Equity (CAPM):
=Risk_Free_Rate + Beta * Equity_Risk_PremiumSensitivity analysis
Two-variable data table for DCF
Set up a matrix with WACC values across the top and terminal growth rates down the side, with the implied share price formula in the top-left corner.
WACC
8% 9% 10% 11% 12%
Growth 1% $XX $XX $XX $XX $XX
2% $XX $XX $XX $XX $XX
3% $XX $XX $XX $XX $XXUse Excel's Data Table feature (Data > What-If > Data Table) to fill the matrix automatically.
Scenario toggle pattern
Scenario_Selector: [dropdown: 1=Base, 2=Bull, 3=Bear]
Revenue_Growth: =CHOOSE(Scenario_Selector, 0.05, 0.10, 0.02)
EBITDA_Margin: =CHOOSE(Scenario_Selector, 0.20, 0.25, 0.15)
CapEx_Pct: =CHOOSE(Scenario_Selector, 0.05, 0.04, 0.06)This lets the entire model recalculate by changing a single cell.
Depreciation schedules
Straight-line
=Asset_Cost / Useful_LifeDeclining balance (double declining)
Year 1: =Asset_Cost * (2 / Useful_Life)
Year N: =MAX(0, Prior_Book_Value * (2 / Useful_Life))Switch to straight-line when straight-line depreciation exceeds declining balance.
Sum-of-years-digits
=Asset_Cost * (Remaining_Life / Sum_Of_Years)
where Sum_Of_Years = Useful_Life * (Useful_Life + 1) / 2Debt schedule
Beginning Balance
+ New Borrowings
- Scheduled Repayments
- Optional Prepayments (from excess cash)
= Ending Balance
Interest Expense = Beginning_Balance * Interest_RateFor revolving credit facilities, model the draw as:
=MAX(0, -Cash_Before_Revolver)This creates a circular reference (interest affects cash, cash affects revolver draw). Resolve by enabling iterative calculation or using a prior-period approximation.
Common financial modeling mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Hardcoded growth rates in formulas | Cannot run scenarios or audit assumptions | All assumptions on a dedicated Assumptions sheet |
| Balance sheet does not balance | Indicates a structural error in the model | Add a balance check row with conditional formatting |
| Mixing real and nominal values | Overstates or understates projections | Be consistent - use nominal throughout or real throughout |
| Forgetting working capital changes | Overstates free cash flow | Always include AR, inventory, AP changes in cash flow |
| Terminal value dominates DCF (>75% of EV) | Model is not useful - all value is in one speculative number | Extend the explicit forecast period or cross-check with multiples |
| Circular references from revolver/interest | Model may not converge or gives unstable results | Enable iterative calc with max 100 iterations, or use prior-period interest |
formula-patterns.md
Advanced Formula Patterns
LET - Name intermediate calculations
LET eliminates repeated sub-expressions and makes complex formulas readable.
=LET(
revenue, SUMIFS(Sales[Amount], Sales[Region], A1),
cost, SUMIFS(Sales[Cost], Sales[Region], A1),
margin, revenue - cost,
margin_pct, IF(revenue=0, 0, margin / revenue),
TEXT(margin_pct, "0.0%")
)Use LET whenever a sub-expression appears more than once, or when naming an intermediate step improves readability.
LAMBDA - Reusable custom functions (no VBA)
LAMBDA lets you define custom functions in the Name Manager without VBA.
Define in Name Manager (Formulas > Name Manager > New):
Name: TAX
Refers to: =LAMBDA(amount, rate, amount * rate)Use in cells:
=TAX(B5, Tax_Rate)LAMBDA with MAP for row-by-row processing:
=MAP(Sales[Amount], LAMBDA(amt, IF(amt > 1000, amt * 0.9, amt)))LAMBDA with REDUCE for accumulation:
=REDUCE(0, Sales[Amount], LAMBDA(acc, amt, acc + amt))Dynamic array functions (Excel 365+ / Google Sheets)
These functions return arrays that "spill" across multiple cells.
FILTER - Extract rows matching criteria
=FILTER(Data, (Data[Region]="West") * (Data[Revenue]>10000), "No results")Multiple criteria use * for AND, + for OR:
=FILTER(Data, (Data[Status]="Active") + (Data[Status]="Pending"))SORT and SORTBY
=SORT(FILTER(Data, Data[Region]="West"), 3, -1)SORTBY sorts by a column that may not be in the output:
=SORTBY(Data[Name], Data[Revenue], -1)UNIQUE - Deduplicate
=UNIQUE(Data[Region])Unique rows (all columns must match):
=UNIQUE(A2:C100)SEQUENCE - Generate number series
=SEQUENCE(12, 1, 1, 1) -- 1 to 12 in a column
=SEQUENCE(1, 10, 0, 0.1) -- 0.0 to 0.9 in a row
=DATE(2025, SEQUENCE(12), 1) -- First of each month in 2025CHOOSECOLS / CHOOSEROWS - Select specific columns or rows
=CHOOSECOLS(Data, 1, 3, 5) -- Return columns 1, 3, 5 only
=CHOOSEROWS(Data, 1, -1) -- First and last rowText manipulation formulas
TEXTJOIN - Concatenate with delimiter
=TEXTJOIN(", ", TRUE, FILTER(Data[Name], Data[Region]="West"))TEXTSPLIT (Excel 365) - Split delimited text
=TEXTSPLIT(A1, ",") -- Split by comma into columns
=TEXTSPLIT(A1, , CHAR(10)) -- Split by newline into rowsREGEXMATCH / REGEXEXTRACT / REGEXREPLACE (Google Sheets only)
=REGEXMATCH(A1, "^\d{3}-\d{4}$")
=REGEXEXTRACT(A1, "(\d+\.?\d*)")
=REGEXREPLACE(A1, "\s+", " ")Excel has no native regex. Use LAMBDA + MID + SEQUENCE for pattern matching,
or VBA's RegExp object for complex patterns.
Date and time patterns
EOMONTH - End of month arithmetic
=EOMONTH(TODAY(), 0) -- Last day of current month
=EOMONTH(TODAY(), -1) + 1 -- First day of current month
=EOMONTH(A1, 0) - EOMONTH(A1, -1) -- Days in the month of A1NETWORKDAYS - Business day calculations
=NETWORKDAYS(start_date, end_date, holidays_range)
=WORKDAY(start_date, 10, holidays_range) -- 10 business days from startFiscal year / quarter mapping
=LET(
month, MONTH(A1),
fiscal_month, MOD(month - fiscal_start_month, 12) + 1,
fiscal_quarter, ROUNDUP(fiscal_month / 3, 0),
"Q" & fiscal_quarter
)Array formula patterns (legacy CSE)
For older Excel versions without dynamic arrays, use Ctrl+Shift+Enter (CSE):
Multi-criteria SUMPRODUCT (works everywhere):
=SUMPRODUCT((Data[Region]="West") * (Data[Year]=2025) * Data[Revenue])Conditional array count of unique values:
=SUMPRODUCT((Data[Region]="West") / COUNTIF(
IF(Data[Region]="West", Data[Product]),
IF(Data[Region]="West", Data[Product])
))SUMPRODUCT does not need CSE entry and works in all Excel versions. Prefer it over CSE array formulas for maximum compatibility.
Error handling patterns
Nested IFERROR for fallback chains
=IFERROR(XLOOKUP(A1, Primary[ID], Primary[Value]),
IFERROR(XLOOKUP(A1, Secondary[ID], Secondary[Value]),
"Not found in any source"))IFNA vs IFERROR
Use IFNA when you only want to catch #N/A (lookup miss). Use IFERROR when any error type should be handled. IFNA is safer because it does not mask unexpected errors like #REF! or #VALUE! that indicate real problems.
=IFNA(XLOOKUP(A1, range, range), 0) -- Only catches #N/A
=IFERROR(complex_formula, "Error") -- Catches everything (use carefully)ISERROR / ISNA for conditional logic
=IF(ISNA(MATCH(A1, range, 0)), "New item", "Existing") vba-patterns.md
VBA and Apps Script Patterns
VBA fundamentals
Loop through rows efficiently
Sub ProcessRows()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim i As Long
For i = 2 To lastRow
' Read values
Dim name As String
name = ws.Cells(i, 1).Value
Dim amount As Double
amount = ws.Cells(i, 2).Value
' Write result
ws.Cells(i, 3).Value = amount * 1.1
Next i
End SubUse arrays for speed (avoid cell-by-cell reads)
Sub FastProcess()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Data")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
' Read entire range into array (much faster than cell-by-cell)
Dim data As Variant
data = ws.Range("A2:D" & lastRow).Value
Dim results() As Variant
ReDim results(1 To UBound(data, 1), 1 To 1)
Dim i As Long
For i = 1 To UBound(data, 1)
results(i, 1) = data(i, 2) * data(i, 3) ' Price * Quantity
Next i
' Write results back in one operation
ws.Range("E2:E" & lastRow).Value = results
End SubReading/writing arrays is 10-100x faster than cell-by-cell operations. Always use this pattern for datasets larger than ~100 rows.
Error handling
Sub SafeOperation()
On Error GoTo ErrorHandler
' ... risky operations ...
Exit Sub
ErrorHandler:
MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical
' Optionally log to a sheet
Dim logWs As Worksheet
Set logWs = ThisWorkbook.Sheets("Log")
Dim logRow As Long
logRow = logWs.Cells(logWs.Rows.Count, "A").End(xlUp).Row + 1
logWs.Cells(logRow, 1).Value = Now()
logWs.Cells(logRow, 2).Value = Err.Description
End SubScreen updating and calculation toggles
Sub OptimizedMacro()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
' ... bulk operations ...
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End SubAlways re-enable these in a Finally-style pattern. If an error occurs before re-enabling, Excel stays in manual calc mode until the user notices.
VBA UserForms
Basic input form pattern
' In UserForm code module
Private Sub btnSubmit_Click()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Data")
Dim nextRow As Long
nextRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
ws.Cells(nextRow, 1).Value = txtName.Value
ws.Cells(nextRow, 2).Value = CDbl(txtAmount.Value)
ws.Cells(nextRow, 3).Value = cboCategory.Value
ws.Cells(nextRow, 4).Value = Now()
MsgBox "Record added.", vbInformation
Unload Me
End Sub
Private Sub UserForm_Initialize()
' Populate combo box
cboCategory.AddItem "Revenue"
cboCategory.AddItem "Expense"
cboCategory.AddItem "Transfer"
End SubValidate form inputs
Private Sub btnSubmit_Click()
If Trim(txtName.Value) = "" Then
MsgBox "Name is required.", vbExclamation
txtName.SetFocus
Exit Sub
End If
If Not IsNumeric(txtAmount.Value) Then
MsgBox "Amount must be a number.", vbExclamation
txtAmount.SetFocus
Exit Sub
End If
' ... proceed with submission ...
End SubVBA API calls (HTTP requests)
GET request using XMLHTTP
Function FetchJSON(url As String) As String
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", url, False
http.setRequestHeader "Content-Type", "application/json"
http.send
If http.Status = 200 Then
FetchJSON = http.responseText
Else
FetchJSON = "Error: " & http.Status & " " & http.statusText
End If
End FunctionParse JSON response (using VBA-JSON library)
' Requires: https://github.com/VBA-tools/VBA-JSON imported as JsonConverter module
Sub ImportAPIData()
Dim json As String
json = FetchJSON("https://api.example.com/data")
Dim parsed As Object
Set parsed = JsonConverter.ParseJson(json)
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Import")
Dim row As Long
row = 2
Dim item As Variant
For Each item In parsed
ws.Cells(row, 1).Value = item("id")
ws.Cells(row, 2).Value = item("name")
ws.Cells(row, 3).Value = item("value")
row = row + 1
Next item
End SubGoogle Apps Script patterns
Read and write ranges
function processData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Data");
// Read all data at once (fast)
const data = sheet.getDataRange().getValues();
const headers = data[0];
// Process rows
const results = data.slice(1).map(row => {
const revenue = row[1];
const cost = row[2];
return [revenue - cost, (revenue - cost) / revenue];
});
// Write results in one call (fast)
const outputRange = sheet.getRange(2, headers.length + 1, results.length, 2);
outputRange.setValues(results);
}Like VBA arrays, batch
getValues()/setValues()is orders of magnitude faster than cell-by-cellgetValue()/setValue().
Custom menu
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Custom Tools")
.addItem("Refresh Data", "refreshData")
.addItem("Send Report", "sendWeeklyReport")
.addSeparator()
.addItem("Archive Old Rows", "archiveRows")
.addToUi();
}Fetch external API data
function importFromAPI() {
const url = "https://api.example.com/data";
const options = {
method: "get",
headers: { "Authorization": "Bearer " + getApiKey() },
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() !== 200) {
throw new Error("API error: " + response.getResponseCode());
}
const data = JSON.parse(response.getContentText());
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Import");
// Clear old data and write new
sheet.getRange("A2:Z").clearContent();
const rows = data.map(item => [item.id, item.name, item.value, new Date()]);
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, rows[0].length).setValues(rows);
}
}
function getApiKey() {
return PropertiesService.getScriptProperties().getProperty("API_KEY");
}Store API keys in Script Properties (File > Project properties > Script properties), never hardcoded in the script.
Scheduled triggers
function createScheduledTriggers() {
// Delete existing triggers first
ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
// Daily data refresh at 6 AM
ScriptApp.newTrigger("importFromAPI")
.timeBased()
.everyDays(1)
.atHour(6)
.create();
// Weekly report every Monday at 9 AM
ScriptApp.newTrigger("sendWeeklyReport")
.timeBased()
.everyWeeks(1)
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(9)
.create();
}Sidebar / dialog UI
function showSidebar() {
const html = HtmlService.createHtmlOutput(`
<h3>Data Filter</h3>
<label>Region:</label>
<select id="region">
<option>All</option>
<option>West</option>
<option>East</option>
</select>
<br><br>
<button onclick="google.script.run.filterByRegion(
document.getElementById('region').value)">
Apply Filter
</button>
`).setTitle("Filter Panel");
SpreadsheetApp.getUi().showSidebar(html);
}Common gotchas
| Issue | Platform | Solution |
|---|---|---|
| VBA array is 1-based after Range.Value read | Excel VBA | Use LBound/UBound, not hardcoded indices |
| Apps Script 6-minute timeout | Google Sheets | Break work into batches, use continuation with PropertiesService |
| XMLHTTP blocked by CORS/firewall | Excel VBA | Use MSXML2.ServerXMLHTTP or WinHttp.WinHttpRequest instead |
| Trigger quota (20 triggers per user) | Google Sheets | Consolidate triggers into fewer functions that dispatch internally |
| Macro security blocks execution | Excel VBA | Save as .xlsm, enable macros, or sign the VBA project |
Frequently Asked Questions
What is spreadsheet-modeling?
Use this skill when building, auditing, or optimizing spreadsheet models in Excel or Google Sheets. Triggers on formula writing, pivot table creation, dashboard design, data validation, conditional formatting, macro/VBA scripting, Apps Script automation, financial modeling, what-if analysis, XLOOKUP/INDEX-MATCH lookups, array formulas, and workbook architecture. Covers advanced Excel and Google Sheets for analysts, finance professionals, and operations teams.
How do I install spreadsheet-modeling?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill spreadsheet-modeling in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support spreadsheet-modeling?
spreadsheet-modeling works with claude-code, gemini-cli, openai-codex, mcp. Install it once and use it across any supported AI coding agent.