Library / SDK
qax-os/excelize avatar
qax-os/excelize

qax-os/excelize: writing XLSX files from Go without a spreadsheet engine

Go language library for reading and writing Microsoft Excel™ (XLAM / XLSM / XLSX / XLTM / XLTX) spreadsheets

20,950 stars1,964 forksGoBSD-3-Clause

At a glance

What is it?
Excelize is a pure Go library for reading and writing XLSX and related OOXML formats. It fits Go services that generate or parse workbooks in-process, and it is the wrong choice when you need to recalculate formulas or render a sheet exactly as Excel would.
Who is it for?
Adopt Excelize if you are writing a Go service that must produce or parse XLSX files in-process and you are willing to treat the OOXML package as the source of truth. Do not adopt it if your workflow depends on Excel recalculating formulas for you, or if you need pixel-accurate rendering: the library writes the formula string, and the README does not document a calculation engine.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Excelize is for, and who actually needs it

The problem Excelize solves is mundane and recurring: a Go program needs to produce a file that a finance team, a data analyst or a customer can open in Excel, and the program has no Excel installation to call. The README describes the library as "written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX files", supporting documents "generated by Microsoft Excel 2007 and later". That is the whole pitch. There is no server, no runtime, no COM automation, no headless LibreOffice process to supervise.

The audience follows from that. Backend Go services that emit reports, exporters that turn query results into a workbook, and batch jobs that read uploaded spreadsheets all fit. So do CLI tools where a single static binary matters. The library also covers worksheet-level features that are easy to underestimate: charts, pictures, data validation, pivot tables, slicers, merged cells, number formats and conditional formatting all appear as separate source files in the repository (chart.go, picture.go, datavalidation.go, pivotTable.go, slicer.go, merge.go, numfmt.go).

It is not for people who want to compute with spreadsheets. Excelize manipulates the file format. If your task is "evaluate this formula over this data", you are looking at the wrong library.

The OOXML package is the data model

An XLSX file is a ZIP archive of XML parts, and Excelize is organised around that fact. The repository layout mirrors the parts rather than presenting an abstract workbook object: docProps.go handles document properties, calcchain.go the calculation chain, sheetview.go and sheetpr.go the sheet-level view and properties, drawing.go the drawing layer, crypt.go the encrypted-package path. The go.mod file lists github.com/richardlehane/mscfb, which is the compound file binary reader Excelize uses for the older container formats, alongside github.com/xuri/efp and github.com/xuri/nfp for formula and number-format parsing. That dependency list is a fair summary of the architecture: parse the container, parse the XML, expose typed Go accessors over it.

The streaming API is the other half of the design. The README states the library "provided streaming API for generating or reading data from a worksheet with huge amounts of data". The ordinary path, NewFile and SetCellValue, holds the workbook in memory; the streaming path trades random access for a bounded footprint. Which one you pick is a real decision, not a style preference, and the README does not quantify the crossover point.

One constraint is stated plainly and is easy to miss: "This library needs Go version 1.25.0 or later." The go.mod confirms it with a go 1.25.0 directive. If your build image is pinned to an older toolchain, that is the first thing that breaks.

Installing Excelize and writing a first workbook

Installation is a single module fetch. The README gives the plain command and then a Go Modules variant, and the second is the one you want in a modern project because the module path carries the /v2 major-version suffix.

bash
go get github.com/xuri/excelize/v2

After that, the minimal program below is adapted from the README's create-spreadsheet example. It builds a workbook, adds a second sheet, writes a string and a number, marks the new sheet active, and saves to a path on disk.

go
package main

import (
	"fmt"

	"github.com/xuri/excelize/v2"
)

func main() {
	f := excelize.NewFile()
	defer func() {
		if err := f.Close(); err != nil {
			fmt.Println(err)
		}
	}()
	index, err := f.NewSheet("Sheet2")
	if err != nil {
		fmt.Println(err)
		return
	}
	f.SetCellValue("Sheet2", "A2", "Hello world.")
	f.SetCellValue("Sheet1", "B2", 100)
	f.SetActiveSheet(index)
	if err := f.SaveAs("Book1.xlsx"); err != nil {
		fmt.Println(err)
	}
}

Run it and you should find Book1.xlsx in the working directory, with Sheet2 active and A2 containing the string. Note the deferred Close: the README's examples consistently pair every NewFile or OpenFile with a Close, and SaveAs is separate from Close.

Reading is the mirror image. OpenFile returns a file handle, GetCellValue reads one cell by worksheet name and cell reference, and GetRows returns all rows in a sheet.

go
f, err := excelize.OpenFile("Book1.xlsx")
if err != nil {
	fmt.Println(err)
	return
}
defer func() {
	if err := f.Close(); err != nil {
		fmt.Println(err)
	}
}()
cell, err := f.GetCellValue("Sheet1", "B2")
if err != nil {
	fmt.Println(err)
	return
}
fmt.Println(cell)

The README's reading example prints 100 for B2 in the workbook written above. GetRows is the convenient call for small sheets and the wrong one for large ones.

Charts and images are first-class, and that is where the API gets verbose

The README devotes full examples to charts and pictures, and the shape of those examples tells you something about the cost. Adding a 3D clustered column chart means constructing an excelize.Chart value with a Type constant (excelize.Col3DClustered in the README's example), a slice of excelize.ChartSeries where each series names its Name, Categories and Values as A1-style range strings, and a Title containing a Paragraph of excelize.RichTextRun. Data goes in through SetSheetRow with a row pointer, and cell addresses are produced by excelize.CoordinatesToCellName.

That is a faithful model of the underlying chart XML, and it is verbose for the same reason. The series ranges are strings, so a typo in "Sheet1!$B$2:$D$2" fails at chart-render time in Excel rather than at compile time. Nothing in the README suggests the library validates those references for you.

Pictures are simpler and more interesting. AddPicture takes a worksheet, a cell anchor, an image path and an optional *excelize.GraphicOptions. The README's example passes ScaleX and ScaleY for scaling, and for the third image sets PrintObject, LockAspectRatio, OffsetX, OffsetY and Locked. Those options map to spreadsheet drawing semantics rather than image semantics, which is why LockAspectRatio and Locked sit next to each other and mean different things. The example also blank-imports image/gif, image/jpeg and image/png, so the decoder registration is the caller's responsibility. Forget one and the corresponding format will not decode.

Limitations: no calculation engine, and an undocumented performance edge

The strongest limitation is the one the README never claims to solve. Excelize reads and writes formulas, and the repository contains calc.go and calcchain.go, but the README does not document a formula evaluation engine, and it does not promise that a formula written by the library will have a cached result. If your pipeline writes =SUM(B2:D2) and then reads the cell back without Excel having opened the file, you should not assume a number comes back. Treat the formula string as data and verify the round trip on your own files before you build on it.

The second limitation is memory. The streaming API exists precisely because the default path holds the workbook in memory, and the README does not state a size threshold at which you must switch. That is a gap, not a defect, but it means the decision is yours to make empirically. Sparse sheets make it worse: GetRows returns a rectangular view, and a sheet with data in A1 and ZZ10000 is not the same shape in memory as it is on disk.

The third is format coverage. The README names XLAM, XLSM, XLSX, XLTM and XLTX. The older binary .xls format is not in that list, and the presence of mscfb in go.mod is about reading the OOXML container, not about supporting the legacy binary workbook format. If your users still upload .xls files, this is the wrong tool and you need a converter in front of it.

Excelize against xlsx-style libraries in other languages

The realistic alternative for most teams is not another Go library but a different runtime. In Node.js, ExcelJS and SheetJS both read and write XLSX and both expose a worksheet object model; in Python, openpyxl occupies the same position. The difference in approach is packaging and the type system, not the file format. Excelize compiles into a single Go binary with no runtime dependency, which matters for CLI tools and for services that must not ship a Node or Python interpreter. The JavaScript and Python options give you a REPL and a much larger pool of developers who already know the API.

A closer comparison is with the spreadsheet engine itself. If you need Excel's recalculation, its rendering, or its macro execution, no library in any language substitutes for driving Excel or LibreOffice. Excelize writes the file; it does not become the application.

Within Go, the honest framing is that Excelize is the default answer rather than one of several. The README positions it as pure Go with high compatibility and a streaming API, and the repository's test files sit beside nearly every source file (chart_test.go, pivotTable_test.go, calc_test.go and so on), which is a signal about how the maintainers treat format edge cases. The trade-off you accept is a large API surface that tracks the OOXML specification closely, so simple tasks stay simple and complex ones stay as complex as the format.

Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-10. Releases are infrequent and deliberate: v2.10.0 on 2025-10-13, v2.10.1 on 2026-02-24, and v2.11.0 on 2026-07-06. That cadence suits a format library, where the specification changes slowly and a quiet release usually means fewer regressions rather than abandonment.

The upgrade cost is concentrated in the toolchain requirement and the module path. The module is github.com/xuri/excelize/v2, so the major version is baked into every import line; a future v3 would be a mechanical but repository-wide change. The go 1.25.0 directive in go.mod means an upgrade of Excelize can force an upgrade of your Go toolchain, and the README states the requirement explicitly. Plan for that in CI images rather than discovering it during a dependency bump.

Licensing is BSD-3-Clause, per the LICENSE file and the badge in the README. That is a permissive licence, and the practical implication is that you can use the library in closed-source products provided you keep the copyright notice and licence text with your distribution. This is not legal advice; read LICENSE yourself, and note that the README says nothing about the licence terms of the OOXML formats or of Excel itself, which are separate questions.

Editorial conclusion

Adopt Excelize if you are writing a Go service that must produce or parse XLSX files in-process and you are willing to treat the OOXML package as the source of truth. Do not adopt it if your workflow depends on Excel recalculating formulas for you, or if you need pixel-accurate rendering: the library writes the formula string, and the README does not document a calculation engine. Before committing, verify two things against your own files: that GetRows returns the sheet dimensions you expect on a workbook with sparse rows, and that a formula you write survives a round trip through OpenFile and SaveAs without being rewritten. Also read the BSD-3-Clause text in LICENSE, since the licence governs what you may do with the code and the README does not restate its terms.

Frequently asked questions

Should I use XLS or XLSX with Excelize?

XLSX. The README lists XLAM, XLSM, XLSX, XLTM and XLTX as the supported formats, all of which are OOXML packages, and the older binary XLS format is not among them.

What will open an XLSX file written by Excelize?

The README states the library supports spreadsheet documents generated by Microsoft Excel 2007 and later, so files it writes target that format and are meant to be opened by Excel and other OOXML-compatible readers.

Is an XLSX file just a zip file?

Structurally yes, and Excelize is organised around that: the repository has separate source files for the XML parts of the package, such as docProps.go, calcchain.go, sheetview.go and drawing.go, and it depends on mscfb for the compound file container.

Can I open an XLSX file in Chrome?

The README does not discuss browser viewing. It describes a Go library for reading and writing spreadsheet files, and the related search phrase Excelize-wasm suggests browser use is something people look for, but the README documents no WebAssembly build.

How does golang excelize compare with xlsx libraries?

The README positions Excelize as a pure Go library with high compatibility and a streaming API for worksheets with huge amounts of data. It does not name or compare itself with other XLSX libraries, so any comparison has to come from evaluating the alternatives yourself.

Official sources

  1. License: BSD-3-Clause
  2. Project website
  3. qax-os/excelize on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/qax-os-excelize.svg)](https://hysenlabs.com/projects/qax-os-excelize)