Expert knowledge of Excel Office Scripts — Microsoft's TypeScript automation platform for Excel on the web, including the full ExcelScript API surface, TypeScript 4.0.3 restrictions, Graph API workbook integration, Power Automate connector limits, performance optimization, and common automation patterns.
Resources
2Install
npx skillscat add markus41/claude-m/office-scripts Install via the SkillsCat registry.
Excel Office Scripts
Office Scripts is Microsoft's TypeScript-based automation platform for Excel on the web. Scripts automate Excel tasks and can be triggered manually from the Automate tab or programmatically via Power Automate flows.
When to Activate
- User asks to write, review, or fix an Office Script
- User mentions Excel automation,
.ostsfiles, orExcelScript - User wants TypeScript code that runs inside Excel
- User asks about Power Automate + Excel integration
- User needs to understand Office Script restrictions or best practices
- User asks about the Excel Graph REST API for workbook operations
- User wants to read/write Excel data from an external application
Entry Point — The main Function
Every Office Script must have exactly one entry point:
function main(workbook: ExcelScript.Workbook) {
// Script logic here
}For scripts that call external APIs with fetch, use async:
async function main(workbook: ExcelScript.Workbook) {
let response = await fetch("https://api.example.com/data");
let data: ExternalData[] = await response.json();
}For Power Automate integration, add parameters after workbook:
function main(workbook: ExcelScript.Workbook, sheetName: string, startRow: number) {
let sheet = workbook.getWorksheet(sheetName);
}Object Model Hierarchy
Workbook
├── Worksheet[]
│ ├── Range (cells, rows, columns)
│ ├── Table[]
│ │ ├── TableColumn[]
│ │ ├── TableRow[]
│ │ ├── TableSort
│ │ └── AutoFilter
│ ├── Chart[]
│ │ ├── ChartSeries[]
│ │ ├── ChartAxes
│ │ ├── ChartLegend
│ │ └── ChartTitle
│ ├── PivotTable[]
│ ├── Shape[]
│ ├── Comment[]
│ ├── ConditionalFormat[]
│ └── NamedItem[]
├── NamedItem[] (workbook-scoped)
└── Table[] (workbook-scoped)Collections Pattern
Plural get methods return arrays (never undefined):
let sheets: ExcelScript.Worksheet[] = workbook.getWorksheets();
let tables: ExcelScript.Table[] = sheet.getTables();Singular get methods return object or undefined:
let sheet: ExcelScript.Worksheet | undefined = workbook.getWorksheet("Data");
let table: ExcelScript.Table | undefined = sheet?.getTable("SalesTable");Verify-Before-Use Pattern (Critical)
Always check that objects returned by singular get methods exist before using them:
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getWorksheet("Data");
if (!sheet) {
console.log("Worksheet 'Data' not found");
return;
}
let table = sheet.getTable("SalesTable");
if (!table) {
console.log("Table 'SalesTable' not found");
return;
}
// Safe to use sheet and table here
let range = table.getRangeBetweenHeaderAndTotal();
let values = range.getValues();
}Excel Graph REST API
Office Scripts work inside Excel, but applications can also interact with workbooks via the Microsoft Graph REST API.
Base URL
https://graph.microsoft.com/v1.0Workbook Access Paths
| Path Pattern | Use Case |
|---|---|
/me/drive/items/{itemId}/workbook |
User's OneDrive file by item ID |
/me/drive/root:/{path}:/workbook |
User's OneDrive file by path |
/sites/{siteId}/drive/items/{itemId}/workbook |
SharePoint file by item ID |
/drives/{driveId}/items/{itemId}/workbook |
Any drive by drive ID |
Graph API Endpoints
Session Management
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /workbook/createSession |
Create persistent or non-persistent session |
| POST | /workbook/closeSession |
Close an active session |
| POST | /workbook/refreshSession |
Keep session alive |
Create session body:
{
"persistChanges": true
}Use the returned id in subsequent requests as header: workbook-session-id: {sessionId}.
When to use sessions:
persistChanges: true— Batch multiple writes atomically; changes saved on closepersistChanges: false— Temporary calculations without saving; changes discarded- Sessionless — Simple one-off reads/writes; each request is independent
Session timeout: 5 minutes of inactivity. Refresh to extend.
Worksheet Operations
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /workbook/worksheets |
List all worksheets |
| GET | /workbook/worksheets/{name|id} |
Get worksheet |
| POST | /workbook/worksheets/add |
Add worksheet |
| PATCH | /workbook/worksheets/{id} |
Update worksheet (rename, visibility) |
| DELETE | /workbook/worksheets/{id} |
Delete worksheet |
Add worksheet body:
{
"name": "NewSheet"
}Range Operations
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /workbook/worksheets/{name}/range(address='A1:D10') |
Get range |
| PATCH | /workbook/worksheets/{name}/range(address='A1:D10') |
Update range values/format |
| GET | /workbook/worksheets/{name}/usedRange |
Get used range |
| POST | /workbook/worksheets/{name}/range(address='A1:D10')/clear |
Clear range |
| POST | /workbook/worksheets/{name}/range(address='A1:D10')/merge |
Merge cells |
| POST | /workbook/worksheets/{name}/range(address='A1:D10')/unmerge |
Unmerge cells |
| POST | /workbook/worksheets/{name}/range(address='A1:D10')/sort/apply |
Sort range |
| POST | /workbook/worksheets/{name}/range(address='A1:D10')/insert |
Insert rows/columns |
| POST | /workbook/worksheets/{name}/range(address='A1:D10')/delete |
Delete rows/columns |
Update range values body:
{
"values": [
["Name", "Age", "City"],
["Alice", 30, "Seattle"],
["Bob", 25, "Portland"]
]
}Update range format body:
{
"format": {
"font": {
"bold": true,
"color": "#FFFFFF",
"size": 12
},
"fill": {
"color": "#4472C4"
},
"horizontalAlignment": "Center",
"columnWidth": 120
},
"numberFormat": [["$#,##0.00"]]
}Sort range body:
{
"fields": [
{
"key": 0,
"sortOn": "Value",
"ascending": true
}
],
"matchCase": false,
"hasHeaders": true
}Table Operations
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /workbook/tables |
List workbook tables |
| GET | /workbook/worksheets/{name}/tables |
List worksheet tables |
| POST | /workbook/tables/add |
Create table from range |
| GET | /workbook/tables/{name|id} |
Get table |
| DELETE | /workbook/tables/{name|id} |
Delete table |
| POST | /workbook/tables/{name}/rows/add |
Add table rows |
| GET | /workbook/tables/{name}/rows |
Get all table rows |
| GET | /workbook/tables/{name}/columns |
Get table columns |
| POST | /workbook/tables/{name}/columns/add |
Add table column |
| GET | /workbook/tables/{name}/dataBodyRange |
Get data body range |
| GET | /workbook/tables/{name}/headerRowRange |
Get header row range |
| GET | /workbook/tables/{name}/totalRowRange |
Get total row range |
| POST | /workbook/tables/{name}/sort/apply |
Sort table |
| POST | /workbook/tables/{name}/clearFilters |
Clear all filters |
| POST | /workbook/tables/{name}/reapplyFilters |
Reapply filters |
| PATCH | /workbook/tables/{name}/columns/{id}/filter |
Apply column filter |
Create table body:
{
"address": "Sheet1!A1:D5",
"hasHeaders": true
}Add table rows body:
{
"index": null,
"values": [
["New Item", 100, "2026-03-01", "Active"],
["Another Item", 250, "2026-03-15", "Pending"]
]
}Add table column body:
{
"index": null,
"name": "Status",
"values": [
["Active"],
["Inactive"],
["Pending"]
]
}Chart Operations
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /workbook/worksheets/{name}/charts |
List charts |
| POST | /workbook/worksheets/{name}/charts/add |
Create chart |
| GET | /workbook/worksheets/{name}/charts/{name} |
Get chart |
| PATCH | /workbook/worksheets/{name}/charts/{name} |
Update chart |
| DELETE | /workbook/worksheets/{name}/charts/{name} |
Delete chart |
| GET | /workbook/worksheets/{name}/charts/{name}/image |
Get chart as image |
| POST | /workbook/worksheets/{name}/charts/{name}/setData |
Set chart data source |
Create chart body:
{
"type": "ColumnClustered",
"sourceData": "Sheet1!A1:B5",
"seriesBy": "Auto"
}Chart types: ColumnClustered, ColumnStacked, BarClustered, BarStacked, Line, LineMarkers, Pie, Doughnut, Area, AreaStacked, XYScatter, XYScatterLines, Radar, Surface, Histogram, Waterfall, Treemap, Sunburst, Funnel.
Named Items (Named Ranges)
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /workbook/names |
List workbook named items |
| POST | /workbook/names/add |
Add named item |
| POST | /workbook/names/addFormulaLocal |
Add named formula |
| GET | /workbook/names/{name}/range |
Get range for named item |
Workbook Functions
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /workbook/functions/{functionName} |
Call an Excel function |
Example — VLOOKUP:
POST /workbook/functions/vlookup
{
"lookupValue": "Alice",
"tableArray": {
"address": "Sheet1!A1:D10"
},
"colIndexNum": 3,
"rangeLookup": false
}Supports 300+ Excel functions: SUM, AVERAGE, COUNT, VLOOKUP, IF, CONCATENATE, TEXT, DATE, NOW, etc.
TypeScript 4.0.3 Restrictions (Summary)
Office Scripts uses a restricted TypeScript 4.0.3 environment. Key restrictions:
| Restriction | Details |
|---|---|
No any type |
Explicit or implicit any is forbidden; all variables must be typed |
| No imports/exports | No import, require, or export — scripts are self-contained |
| No external libraries | No npm packages, no DOM APIs, no Node.js APIs |
No eval() |
Dynamic code execution is not allowed |
| No generator functions | function* syntax is not supported |
| Arrow functions | Only allowed as callbacks (e.g., Array.filter, Array.map) |
| No classes | Class declarations are not supported; use interfaces and functions |
| No enums (custom) | You cannot declare your own enums; use ExcelScript enums only |
No any casts |
as any or <any> casts are not allowed |
See references/constraints-and-best-practices.md for the complete list with examples.
Key API Patterns (Quick Reference)
Reading and Writing Data
// Single cell
let value = sheet.getRange("A1").getValue();
sheet.getRange("A1").setValue("Hello");
// Range of cells (2D array)
let values = sheet.getRange("A1:C3").getValues();
sheet.getRange("A1:C3").setValues([
["Name", "Age", "City"],
["Alice", 30, "NYC"],
["Bob", 25, "LA"]
]);
// Dynamic sizing — use getUsedRange()
let usedRange = sheet.getUsedRange();
let allData = usedRange.getValues();Tables
// Create table from range
let table = sheet.addTable(sheet.getRange("A1:C10"), true /* hasHeaders */);
table.setName("SalesData");
// Add row
table.addRow(-1, ["New Item", 100, "2024-01-15"]);
// Filter
table.getColumnByName("Status").getFilter().applyValuesFilter(["Active"]);
// Sort
table.getSort().apply([{ key: 0, ascending: true }]);Formatting
let range = sheet.getRange("A1:D1");
range.getFormat().getFill().setColor("#4472C4");
range.getFormat().getFont().setBold(true);
range.getFormat().getFont().setColor("white");
range.setNumberFormat("$#,##0.00");Conditional Formatting
// Add cell value rule
let cf = sheet.getRange("B2:B100").addConditionalFormat(ExcelScript.ConditionalFormatType.cellValue);
cf.getCellValue().setRule({
formula1: "=0",
operator: ExcelScript.ConditionalCellValueOperator.greaterThan
});
cf.getCellValue().getFormat().getFont().setColor("#006100");
cf.getCellValue().getFormat().getFill().setColor("#C6EFCE");PivotTables
// Create pivot table
let pivotTable = sheet.addPivotTable(
"SalesPivot",
sourceSheet.getRange("A1:E100"),
sheet.getRange("A1")
);
// Add fields
pivotTable.addRowHierarchy(pivotTable.getHierarchy("Region"));
pivotTable.addColumnHierarchy(pivotTable.getHierarchy("Quarter"));
pivotTable.addDataHierarchy(pivotTable.getHierarchy("Revenue"));Power Automate Integration
- Use the Excel Online (Business) connector's "Run script" action
- Parameters:
string,number,boolean, objects, arrays (noany, noRange) - Return values via
returnstatement with typed result fetchis NOT available when called from Power Automatethrowstops the entire flow;returndoes not- JSDoc
@paramcomments appear in the Power Automate UI
Connector Limits
| Limit | Value |
|---|---|
| API calls per day | 1,600 |
| Script execution timeout | 120 seconds |
| Maximum parameter size | 30 MB |
| Maximum return value size | 5 MB |
| Concurrent runs per workbook | 1 (queued) |
See references/power-automate.md for complete integration guide.
Required Permissions
Office Scripts (In-Browser)
| Requirement | Details |
|---|---|
| License | Microsoft 365 Business Standard/Premium, E3, or E5 |
| Platform | Excel on the web (not desktop) |
| Admin setting | Office Scripts must be enabled in M365 admin center |
| Storage | OneDrive or SharePoint Online |
Graph API (External Access)
| Permission | Type | Purpose |
|---|---|---|
Files.ReadWrite |
Delegated | Read/write user's files |
Files.ReadWrite.All |
Application | Read/write all files (app-only) |
Sites.ReadWrite.All |
Delegated/Application | Access SharePoint-hosted workbooks |
Error Handling
Office Script Errors
| Error | Cause | Fix |
|---|---|---|
Object is undefined |
Singular get returned undefined |
Always null-check before use |
Script exceeds size limit |
Script > 100KB | Split into smaller scripts |
Script timed out |
Execution > 120s (PA) or 5 min (manual) | Optimize reads/writes, reduce data volume |
Implicit any |
Untyped variable or destructuring | Add explicit type annotations |
Cannot use import |
import statement used |
Remove — scripts are self-contained |
Graph API Errors
| Status | Error Code | Cause |
|---|---|---|
| 400 | InvalidArgument |
Invalid range address, bad parameter |
| 404 | ItemNotFound |
Worksheet, table, or named item not found |
| 409 | Conflict |
Session conflict — another session is active |
| 409 | InvalidSessionAccessConflict |
Multiple concurrent sessions on same workbook |
| 429 | Too Many Requests | Throttled — honor Retry-After header |
| 504 | Gateway Timeout | Large workbook operation timeout — use sessions |
Session Error Recovery
// If session expires, create a new one and retry
try {
await graphClient.api(url).header("workbook-session-id", sessionId).patch(body);
} catch (err) {
if (err.code === "InvalidSessionAccessConflict" || err.code === "SessionNotFound") {
const newSession = await graphClient.api(`${workbookUrl}/createSession`)
.post({ persistChanges: true });
sessionId = newSession.id;
await graphClient.api(url).header("workbook-session-id", sessionId).patch(body);
}
}Performance Best Practices
- Minimize read/write calls — read all data at once, process in memory, write once
- Read outside loops — never call
getValues()inside a loop - Remove
console.login production — logging impacts performance - Pause calculation for bulk writes:
workbook.getApplication().setCalculationMode(ExcelScript.CalculationMode.manual); // ... bulk operations ... workbook.getApplication().setCalculationMode(ExcelScript.CalculationMode.automatic); - Batch large datasets — process in chunks of ~5,000 rows
- Avoid modifying table structure in loops — delete and recreate instead
- Use sessions for Graph API batch operations — reduces round trips
- Prefer
getUsedRange()over fixed ranges — adapts to data size dynamically
See references/constraints-and-best-practices.md for all performance tips.
Common Patterns
Pattern 1: Data Extraction and Report
getWorksheet("RawData")→getUsedRange()→getValues()— extract raw data- Process in memory: filter rows, aggregate totals, calculate percentages
addWorksheet("Report")— create report sheetgetRange("A1:...")→setValues(reportData)— write processed data- Apply formatting: headers, number formats, conditional formatting
- Optional: create chart from report data
Pattern 2: Data Validation and Cleanup
- Read all data with
getUsedRange().getValues() - Validate each row: check required fields, validate email/phone patterns
- Build
errors[]array with[row, column, message]for each issue - Write errors to a "Validation Results" sheet
- Highlight invalid cells with red fill in the original data
- Return summary:
{ totalRows, validRows, errorRows }
Pattern 3: Multi-Sheet Consolidation
workbook.getWorksheets()— get all sheets- For each sheet:
getUsedRange().getValues()— read data (skip header for 2nd+ sheet) - Concatenate all rows into single 2D array
- Write to a "Consolidated" worksheet
- Convert consolidated range to a Table for sorting/filtering
- Add PivotTable for summary analysis
Pattern 4: Scheduled Data Refresh (via Power Automate)
- Power Automate trigger: Recurrence (daily at 8 AM)
- HTTP action: fetch data from external API
- "Run script" action: pass API response as parameter
- Script: parse response, clear old data, write new rows
- Script: update "Last Updated" cell with timestamp
- Script: return row count for flow logging
Pattern 5: Template-Based Report Generation
- Read template sheet with pre-built formatting and formulas
worksheet.copy()— duplicate template for each report instance- Populate data cells (leave formulas intact)
- Update named ranges for dynamic chart data sources
- Protect sheets to prevent accidental formula deletion
- Return list of generated sheet names
File Storage
- Scripts saved as
.ostsfiles in OneDrive:/Documents/Office Scripts/ - SharePoint-based scripts stored in document library's
Office Scriptsfolder - Scripts can be shared with a workbook or kept personal
Common Gotchas
getRange()vsgetRangeByIndexes():getRange("A1:C3")uses A1 notation;getRangeByIndexes(row, col, rowCount, colCount)uses zero-based indices- Values are 2D arrays: Even a single cell returns
(string | number | boolean)[][] - Dates are serial numbers: Excel stores dates as numbers; use
getNumberFormatLocal()to distinguish - Tables can't be resized: Delete and recreate to change structure
- No implicit
any: Destructuring and untyped function parameters will fail undefinedvsnull: Singulargetmethods returnundefined, notnull- Graph API range address format: Must include sheet name in cross-sheet references (
Sheet1!A1:D10) - Session concurrency: Only one persistent session per workbook at a time
Number Format Reference
| Format String | Example Output | Use Case |
|---|---|---|
#,##0 |
1,234 |
Integer with thousands separator |
#,##0.00 |
1,234.56 |
Two decimal places |
$#,##0.00 |
$1,234.56 |
Currency |
0% |
75% |
Percentage (whole) |
0.00% |
75.50% |
Percentage (decimal) |
yyyy-mm-dd |
2026-03-01 |
ISO date |
mm/dd/yyyy |
03/01/2026 |
US date |
hh:mm:ss |
14:30:00 |
Time |
@ |
(text) | Force text format |
Reference Files
| Resource | Path | Content |
|---|---|---|
| API Patterns | references/api-patterns.md |
Full API surface: Workbook, Worksheet, Range, Table, Chart, etc. |
| Power Automate | references/power-automate.md |
Parameters, returns, connector usage, limits |
| Constraints | references/constraints-and-best-practices.md |
TS 4.0.3 restrictions, platform limits, performance |
| Range Examples | examples/range-operations.md |
Read/write, formatting, formulas, iteration |
| Table Examples | examples/table-operations.md |
Create, filter, sort, format, dynamic formulas |
| Chart Examples | examples/chart-operations.md |
All chart types, customization, data labels |
| Complete Scripts | examples/complete-scripts.md |
Full real-world scripts |