Skip to content

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