# "Excel Spreadsheets"
_Path: en/lua/data/excel_
> "Read and write Microsoft Excel files (.xlsx). Create workbooks, manage sheets, read cell values, and generate reports with formatting support."
## Table of Contents
- Excel Spreadsheets
## Content
# Excel Spreadsheets
Read and write Microsoft Excel files (.xlsx). Create workbooks, manage sheets, read cell values, and generate reports with formatting support.
## Loading
```lua
local excel = require("excel")
```
### New Workbook
Creates a new empty Excel workbook.
```lua
local wb, err = excel.new()
if err then
return nil, err
end
-- Create sheets and add data
wb:new_sheet("Report")
wb:set_cell_value("Report", "A1", "Title")
wb:close()
```
**Returns:** `Workbook, error`
### Open Workbook
Opens an Excel workbook from a reader object.
```lua
local fs = require("fs")
local vol, err = fs.get("app:data")
if err then
return nil, err
end
local file, err = vol:open("/reports/sales.xlsx", "r")
if err then
return nil, err
end
local wb, err = excel.open(file)
if err then
file:close()
return nil, err
end
-- Read data from workbook
local rows = wb:get_rows("Sheet1")
for i, row in ipairs(rows) do
print("Row " .. i .. ": " .. table.concat(row, ", "))
end
wb:close()
file:close()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `reader` | File | Must implement io.Reader (e.g., fs.File) |
**Returns:** `Workbook, error`
### Create Sheet
Creates a new sheet or returns existing sheet index.
```lua
local wb = excel.new()
-- Create sheets
local idx1 = wb:new_sheet("Summary")
local idx2 = wb:new_sheet("Details")
local idx3 = wb:new_sheet("Charts")
-- If sheet exists, returns its index
local existing = wb:new_sheet("Summary") -- returns same as idx1
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | string | Sheet name |
**Returns:** `integer, error`
### List Sheets
Returns list of all sheet names in workbook.
```lua
local wb = excel.new()
wb:new_sheet("Sales")
wb:new_sheet("Expenses")
wb:new_sheet("Summary")
local sheets = wb:get_sheet_list()
-- sheets = {"Sheet1", "Sales", "Expenses", "Summary"}
for _, name in ipairs(sheets) do
print("Sheet:", name)
end
```
**Returns:** `string[], error`
### Set Cell Value
Sets value of a single cell.
```lua
local wb = excel.new()
wb:new_sheet("Data")
-- Set different value types
wb:set_cell_value("Data", "A1", "Product Name") -- string
wb:set_cell_value("Data", "B1", "Price") -- string
wb:set_cell_value("Data", "C1", "In Stock") -- string
wb:set_cell_value("Data", "A2", "Widget")
wb:set_cell_value("Data", "B2", 29.99) -- number
wb:set_cell_value("Data", "C2", true) -- boolean
wb:set_cell_value("Data", "A3", "Gadget")
wb:set_cell_value("Data", "B3", 49.99)
wb:set_cell_value("Data", "C3", false)
-- Cell references support columns beyond Z
wb:set_cell_value("Data", "AA1", "Extended Column")
wb:set_cell_value("Data", "AB100", "Far cell")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sheet` | string | Sheet name |
| `cell` | string | Cell reference ("A1", "B2", "AA100") |
| `value` | any | string, integer, number, or boolean |
**Returns:** `error`
### Get All Rows
Gets all rows from a sheet as 2D array.
```lua
local wb = excel.new()
wb:new_sheet("Report")
wb:set_cell_value("Report", "A1", "Name")
wb:set_cell_value("Report", "B1", "Score")
wb:set_cell_value("Report", "A2", "Alice")
wb:set_cell_value("Report", "B2", 95)
wb:set_cell_value("Report", "A3", "Bob")
wb:set_cell_value("Report", "B3", 87)
local rows, err = wb:get_rows("Report")
if err then
return nil, err
end
-- rows[1] = {"Name", "Score"}
-- rows[2] = {"Alice", "95"}
-- rows[3] = {"Bob", "87"}
for i, row in ipairs(rows) do
if i == 1 then
print("Headers:", row[1], row[2])
else
print("Data:", row[1], "scored", row[2])
end
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sheet` | string | Sheet name |
**Returns:** `string[][], error`
All cell values returned as strings. Booleans as "TRUE" or "FALSE", numbers as string representation.
### Stream Rows
`wb:rows(sheet)` opens a streaming cursor over one sheet. The sheet is decoded incrementally in constant memory, unlike `get_rows` which materializes the entire sheet:
```lua
local cursor, err = wb:rows("Report")
if err then
return nil, err
end
while true do
local batch, err = cursor:read(500)
if err then
cursor:close()
return nil, err
end
if not batch then
break -- end of sheet
end
for _, row in ipairs(batch) do
process(row)
end
end
cursor:close()
```
| Method | Description |
|--------|-------------|
| `cursor:read(n?)` | Read the next batch of up to `n` rows (default 1, max 10000). Returns `string[][], error`; `nil, nil` at end of sheet |
| `cursor:close()` | Release the cursor (idempotent; cursors also close with the workbook) |
Cell values format identically to `get_rows`. Empty rows come back as empty tables, and trailing empty rows are preserved rather than trimmed. After end-of-sheet or an error, subsequent reads keep returning that same state.
### Write to File
Writes workbook to a writer object.
```lua
local fs = require("fs")
local wb = excel.new()
-- Build report
wb:new_sheet("Monthly Report")
wb:set_cell_value("Monthly Report", "A1", "Month")
wb:set_cell_value("Monthly Report", "B1", "Revenue")
wb:set_cell_value("Monthly Report", "A2", "January")
wb:set_cell_value("Monthly Report", "B2", 45000)
wb:set_cell_value("Monthly Report", "A3", "February")
wb:set_cell_value("Monthly Report", "B3", 52000)
-- Write to file
local vol, err = fs.get("app:output")
if err then
wb:close()
return nil, err
end
local file, err = vol:open("/reports/monthly.xlsx", "w")
if err then
wb:close()
return nil, err
end
local err = wb:write_to(file)
file:close()
wb:close()
if err then
return nil, err
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `writer` | File | Must implement io.Writer (e.g., fs.File) |
**Returns:** `error`
### Serialize to a String
Renders the workbook into an `xlsx` byte string, without a filesystem or a writer. Use it to hand a workbook to an HTTP response, an object store or a queue message.
```lua
local cloudstorage = require("cloudstorage")
local wb = excel.new()
wb:new_sheet("Report")
wb:set_cell_value("Report", "A1", "Total")
wb:set_cell_value("Report", "B1", 45000)
local data, err = wb:bytes()
wb:close()
if err then
return nil, err
end
local storage = cloudstorage.get("app.infra:files")
storage:upload_object("reports/monthly.xlsx", data, {
content_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
})
storage:release()
```
**Returns:** `string, error`
The whole workbook is materialized in memory. `write_to` builds the same in-memory buffer and then copies it to the writer, so it saves the Lua string but does not stream a large workbook.
Calling `bytes()` on a closed workbook returns an `errors.INTERNAL` error.
### Close Workbook
Closes workbook and releases resources.
```lua
local wb = excel.new()
-- ... work with workbook ...
wb:close()
-- Safe to call multiple times
wb:close()
```
**Returns:** `error`
## Errors
| Condition | Kind | Retryable |
|-----------|------|-----------|
| No context | `errors.INTERNAL` | no |
| Invalid workbook | `errors.INVALID` | no |
| Workbook closed | `errors.INTERNAL` (`errors.INVALID` from `rows`) | no |
| Not a writer (`write_to`) | `errors.INTERNAL` | no |
| Not a reader (`open`) | raised as an argument error | no |
| Invalid Excel file | `errors.INTERNAL` | no |
| Non-existent sheet | `errors.INTERNAL` (`errors.INVALID` from `rows`) | no |
| Invalid cell reference | `errors.INTERNAL` | no |
| Write failed | `errors.INTERNAL` | no |
See [Error Handling](lua/core/errors.md) for working with errors.
## See Also
- [Filesystem](lua/storage/filesystem.md) - File operations for reading/writing Excel files
## Navigation
Previous: "Payload Encoding" (lua/data/payload)
Next: "HTTP" (lua/http/http)