Pure Go library for reading, writing, and evaluating ODS (OpenDocument Spreadsheet) files. Zero external dependencies, no LibreOffice required.
- Read and write
.odsfiles (OpenDocument Spreadsheet) - Cell value management with typed getters/setters (string, int, float, bool, date)
- Formula evaluation engine with 45+ spreadsheet functions
- Formula recalculation with dependency graph and circular reference detection
- Auto-recalc mode: formulas update automatically when cell values change
- Sheet management (create, copy, rename, delete)
- Row and column operations (insert, remove, resize, hide/show)
- Column auto-fit (optimal width)
- Cell merging and unmerging
- Cell styling (font, fill, border, alignment)
- Sheet and cell protection
- Conditional formatting (calcext namespace)
- Auto-filter with filter criteria and sort keys
- Embedded images (PNG, JPEG, GIF, BMP) anchored to cells
- Streaming row iterator for large files
- Document properties (title, creator, description)
- Go 1.23+
go get github.com/mukbeast4/go-odspackage main
import (
"fmt"
"log"
ods "github.com/mukbeast4/go-ods"
)
func main() {
f := ods.NewFile()
f.SetCellValue("Sheet1", "A1", "Product")
f.SetCellValue("Sheet1", "B1", "Price")
f.SetCellFloat("Sheet1", "A2", 10)
f.SetCellFloat("Sheet1", "A3", 20)
f.SetCellFloat("Sheet1", "A4", 30)
f.SetCellFormula("Sheet1", "A5", "SUM([.A2:.A4])")
f.RecalcSheet("Sheet1")
val, _ := f.GetCellFloat("Sheet1", "A5")
fmt.Println("Sum:", val) // Sum: 60
if err := f.SaveAs("output.ods"); err != nil {
log.Fatal(err)
}
}The built-in formula engine supports evaluation without LibreOffice:
f := ods.NewFile()
f.SetCellFloat("Sheet1", "A1", 100)
f.SetCellFormula("Sheet1", "B1", "[.A1]*1.2")
f.SetCellFormula("Sheet1", "C1", "IF([.B1]>100;\"over\";\"under\")")
f.RecalcAll()
b1, _ := f.GetCellFloat("Sheet1", "B1") // 120
c1, _ := f.GetCellValue("Sheet1", "C1") // "over"Enable automatic formula recalculation when cell values change:
f.SetAutoRecalc(true)
f.SetCellFloat("Sheet1", "A1", 50)
// All dependent formulas are recalculated immediately| Category | Functions |
|---|---|
| Math | SUM, AVERAGE, MIN, MAX, ABS, ROUND, FLOOR, CEIL, INT, MOD, POWER, SQRT, RAND, RANDBETWEEN |
| Logic | IF, IFS, SWITCH, AND, OR, NOT, IFERROR |
| Text | CONCATENATE, LEN, TRIM, UPPER, LOWER, LEFT, RIGHT, MID, FIND, SEARCH, SUBSTITUTE, REPLACE, TEXT, VALUE |
| Stats | COUNT, COUNTA, COUNTIF, COUNTIFS, MEDIAN, STDEV, STDEVP, VAR, VARP, RANK, LARGE, SMALL, PERCENTILE |
| Conditional | SUMIF, SUMIFS, SUMPRODUCT |
| Lookup | VLOOKUP, HLOOKUP, XLOOKUP, INDEX, MATCH |
| Date/Time | DATE, TODAY, NOW, YEAR, MONTH, DAY |
Convenience wrappers for the most common number formats:
f.SetCellCurrency("Sheet1", "A1", 1234.5, "$") // "$1,234.50"
f.SetCellCurrency("Sheet1", "A2", 999.99, "€") // "€999.99"
f.SetCellPercentage("Sheet1", "B1", 0.25, 0) // "25%"
f.SetCellPercentage("Sheet1", "B2", 0.3333, 2) // "33.33%"
t := time.Date(2024, 3, 15, 9, 30, 0, 0, time.UTC)
f.SetCellDateTime("Sheet1", "C1", t, "DD/MM/YYYY")
f.SetCellDuration("Sheet1", "D1", 90*time.Minute) // stored as PT1H30M0Sf, err := ods.OpenFile("spreadsheet.ods")
if err != nil {
log.Fatal(err)
}
defer f.Close()
val, _ := f.GetCellValue("Sheet1", "A1")
rows, _ := f.GetRows("Sheet1")
for _, row := range rows {
fmt.Println(row)
}f.NewSheet("Data")
f.SetSheetName("Sheet1", "Summary")
f.CopySheet("Data", "DataBackup")
f.DeleteSheet("DataBackup")
sheets := f.GetSheetList() // ["Summary", "Data"]style := &ods.Style{
Font: &ods.Font{
Family: "Arial",
Size: "12pt",
Bold: "bold",
Color: "#FF0000",
Strikethrough: true,
StrikethroughColor: "#000000",
VerticalAlign: "super", // or "sub"
},
Fill: &ods.Fill{Color: "#FFFF00"},
Alignment: &ods.Alignment{
Horizontal: "center",
Rotation: 45, // degrees (0-360, negatives are normalized)
Indent: 2, // 0.25cm increments
},
}
styleID, _ := f.NewStyle(style)
f.SetCellStyle("Sheet1", "A1", "A1", styleID)f.InsertRows("Sheet1", 3, 2) // Insert 2 rows at row 3
f.RemoveRow("Sheet1", 5) // Remove row 5
f.SetColWidth("Sheet1", "B", 5.0) // Set column B width
f.SetRowVisible("Sheet1", 2, false) // Hide row 2
f.SetColVisible("Sheet1", "C", false) // Hide column C
f.SetColAutoFit("Sheet1", "A", true) // Auto-fit column A width
f.SetRowAutoFit("Sheet1", 1, true) // Auto-fit row 1 heightf.SetSheetProtection("Sheet1", true)
protected, _ := f.IsSheetProtected("Sheet1") // true
locked := true
styleID, _ := f.NewStyle(&ods.Style{
Protected: &locked,
})
f.SetCellStyle("Sheet1", "A1", "A1", styleID)rules := []ods.ConditionalRule{
{Value: "of:cell-content()>90", StyleName: "good", BaseCellAddress: "Sheet1.A1"},
{Value: "of:cell-content()<50", StyleName: "bad", BaseCellAddress: "Sheet1.A1"},
}
f.SetConditionalFormat("Sheet1", "A1:A100", rules)
formats, _ := f.GetConditionalFormats("Sheet1")
f.RemoveConditionalFormat("Sheet1", "A1:A100")f.SetAutoFilter("Sheet1", "A1", "D100")
f.SetFilterCriteria("Sheet1", []ods.FilterCriteria{
{Column: 0, Values: []string{"Alice"}},
})
f.SetSort("Sheet1", []ods.SortKey{
{Column: 1, Descending: true},
})
criteria, _ := f.GetFilterCriteria("Sheet1")
sortKeys, _ := f.GetSort("Sheet1")
f.ClearFilterCriteria("Sheet1")
f.RemoveSort("Sheet1")Anchor images (PNG, JPEG, GIF, BMP) to any cell. Format is auto-detected, dimensions are in centimeters, identical binaries are deduplicated.
f.AddImage("Sheet1", "B2", "logo.png", &ods.ImageOptions{Width: 4, Height: 3})
data, _ := os.ReadFile("chart.jpg")
f.AddImageFromBytes("Sheet1", "D5", data, &ods.ImageOptions{
Width: 6, Height: 4, OffsetX: 0.5, OffsetY: 0.25,
})
images, _ := f.GetImages("Sheet1")
for _, img := range images {
fmt.Printf("%s: %s %vx%vcm\n", img.CellRef, img.Format, img.Width, img.Height)
}
f.RemoveImages("Sheet1", "B2")A collection of runnable samples lives under examples/. Each sub-directory is a standalone main package:
go run ./examples/quickstart # cells, formula, save
go run ./examples/formulas # aggregation functions
go run ./examples/styling # fonts, fills, borders, rotation
go run ./examples/autofilter # filter + sort
go run ./examples/streaming # 10k rows via AppendRows + RowIterator
go run ./examples/roundtrip # open -> mutate -> save
go run ./examples/images # embed a PNGSee examples/README.md for a breakdown of each sample.
We welcome contributions! Please read our Contributing Guide and Code of Conduct before submitting a pull request.
To report a security vulnerability, please see our Security Policy.
This project is licensed under the MIT License - see the LICENSE file for details.