Go Office Library — Quick Start
The Go package github.com/yfedoseev/office_oxide/go wraps the office_oxide C FFI via cgo and gives you idiomatic Go types for reading, converting, and editing DOCX / XLSX / PPTX / DOC / XLS / PPT files.
Install
go get github.com/yfedoseev/office_oxide/go@latest
The binding needs the office_oxide C library at link time. Two ways to provide it.
Option 1 — one-liner installer (recommended):
go run github.com/yfedoseev/office_oxide/go/cmd/install@latest
The installer downloads the matching prebuilt liboffice_oxide (and header) for your OS/arch and places it where cgo can find it. Re-run after upgrading to pick up the new ABI.
Option 2 — set cgo flags yourself if you’ve built the library from source (cargo build --release --lib) or have it in a system prefix:
export CGO_CFLAGS="-I/usr/local/include"
export CGO_LDFLAGS="-L/usr/local/lib -loffice_oxide"
Read a document
package main
import (
"fmt"
"log"
officeoxide "github.com/yfedoseev/office_oxide/go"
)
func main() {
doc, err := officeoxide.Open("report.docx")
if err != nil {
log.Fatal(err)
}
defer doc.Close()
text, err := doc.PlainText()
if err != nil {
log.Fatal(err)
}
fmt.Println(text)
}
Or the one-shot helper:
text, err := officeoxide.ExtractText("report.docx")
Core API
doc, err := officeoxide.Open("file.xlsx")
if err != nil { log.Fatal(err) }
defer doc.Close()
format, _ := doc.Format() // "xlsx"
text, _ := doc.PlainText()
md, _ := doc.ToMarkdown()
html, _ := doc.ToHTML()
irJSON, _ := doc.ToIRJSON()
err = doc.SaveAs("file.docx")
Document wraps a C handle; a finalizer frees it if you forget Close, but always prefer defer doc.Close() for deterministic cleanup.
Open from bytes (useful for streaming / serverless):
data, _ := os.ReadFile("report.pptx")
doc, err := officeoxide.OpenFromBytes(data, "pptx")
format must be one of "docx", "xlsx", "pptx", "doc", "xls", "ppt".
Editing
Editable handles preserve every unmodified OPC part (images, charts, custom XML) on save. DOCX, XLSX, and PPTX only.
ed, err := officeoxide.OpenEditable("template.docx")
if err != nil { log.Fatal(err) }
defer ed.Close()
n, _ := ed.ReplaceText("{{name}}", "Alice")
fmt.Printf("%d replacements\n", n)
err = ed.Save("out.docx")
Replace text in DOCX / PPTX
ed, _ := officeoxide.OpenEditable("slides.pptx")
defer ed.Close()
ed.ReplaceText("Q3", "Q4")
ed.ReplaceText("2024", "2025")
buf, _ := ed.SaveToBytes() // []byte ready to upload / stream
_ = os.WriteFile("slides_q4.pptx", buf, 0o644)
Set XLSX cells
ed, _ := officeoxide.OpenEditable("budget.xlsx")
defer ed.Close()
ed.SetCell(0, "A1", officeoxide.NewStringCell("Total"))
ed.SetCell(0, "B1", officeoxide.NewNumberCell(42.5))
ed.SetCell(0, "C1", officeoxide.NewBoolCell(true))
ed.SetCell(0, "D1", officeoxide.NewEmptyCell())
ed.Save("budget.xlsx")
Use NewStringCell, NewNumberCell, NewBoolCell, or NewEmptyCell — the constructor picks the correct variant for the FFI call.
Format-agnostic IR
doc.ToIRJSON() returns JSON matching the Rust DocumentIR schema. Unmarshal into whatever shape you need:
import "encoding/json"
irJSON, _ := doc.ToIRJSON()
var ir struct {
Sections []struct {
Title *string `json:"title"`
Elements []json.RawMessage `json:"elements"`
} `json:"sections"`
}
_ = json.Unmarshal([]byte(irJSON), &ir)
fmt.Printf("%d sections\n", len(ir.Sections))
Detect format without opening
fmt := officeoxide.DetectFormat("mystery.bin") // "" if unsupported
Legacy formats
Open them like OOXML; SaveAs transparently converts through the IR:
doc, _ := officeoxide.Open("old.xls")
defer doc.Close()
_ = doc.SaveAs("modern.xlsx")
Errors
Every fallible call returns an *officeoxide.Error with a typed code plus the originating operation:
if _, err := officeoxide.Open("missing.docx"); err != nil {
var e *officeoxide.Error
if errors.As(err, &e) {
fmt.Printf("code=%d op=%s\n", e.Code, e.Op)
}
}
Using a closed handle returns officeoxide.ErrClosed.
| Code | Name | Meaning |
|---|---|---|
| 0 | OK |
success |
| 1 | INVALID_ARG |
nil / empty / wrong format string |
| 2 | IO |
filesystem error |
| 3 | PARSE |
malformed document |
| 4 | EXTRACTION |
parsing succeeded but rendering failed |
| 5 | INTERNAL |
bug — please file an issue |
| 6 | UNSUPPORTED |
extension / feature not supported |
Troubleshooting
| Symptom | Fix |
|---|---|
could not determine kind of name for C.office_document_open |
Headers aren’t visible to cgo. Run the installer or set CGO_CFLAGS. |
cannot find -loffice_oxide at link time |
Set CGO_LDFLAGS="-L/path/to/lib -loffice_oxide" or run the installer. |
Runtime cannot open shared object file |
Add the library directory to LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (macOS), or copy the DLL next to your binary (Windows). |
unsupported format on .doc/.xls |
Make sure the extension is lowercase, or call OpenFromBytes(data, "doc"). |
| Cross-compilation fails | cgo needs a target-matching C toolchain. Use zig cc or CC=aarch64-linux-gnu-gcc. |
See also
- Performance benchmarks
- Package on pkg.go.dev
- C header —
include/office_oxide_c/office_oxide.h