Generate Legacy .xls Files in Go Without LibreOffice — Introducing RetroXL
Many banks, government portals, and older enterprise systems still require uploads in the legacy .xls Excel format. Go makes it easy to generate modern .xlsx files, but generating real .xls content usually requires installing LibreOffice, Python scripts, COM automation, or external system binaries.
This approach is slow, hard to deploy, and unsuitable for containers or microservice workflows.
To solve this problem, I built RetroXL, a pure-Go library that generates legacy-compatible .xls files from .xlsx, .csv, .tsv, or in-memory data structures.
Repository: https://github.com/mhshajib/retroxl
The Real Problem
If you work with banking integrations, you may have faced this issue:
- Some bank portal rejects
.xlsx - Some bank only accepts
.xls - The validation rules are strict
- The older systems cannot parse modern formats
Most Go libraries output .xlsx. Meanwhile, generating .xls typically requires tools that are heavy, slow, and not ideal for production deployments.
RetroXL addresses this directly.
What RetroXL Does
RetroXL generates .xls files using the SpreadsheetML 2003 (XML) format accepted by Excel as a valid .xls.
Key characteristics:
- Pure Go implementation
- Zero external dependencies or binaries
- Converts
.xlsxto.xls - Converts
.csvand.tsvto.xls - Builds
.xlsfrom in-memory slices or tables - Outputs to file, bytes, or any
io.Writer - Works over HTTP, gRPC, S3, etc.
Installation
go get github.com/mhshajib/retroxl
Example 1: Convert .xlsx to .xls
package main
import (
"log"
"github.com/mhshajib/retroxl"
)
func main() {
err := retroxl.ConvertXLSXToXLSFile("input.xlsx", "output.xls")
if err != nil {
log.Fatalf("convert failed: %v", err)
}
}
Example 2: Generate .xls from a slice of structs
package main
import (
"github.com/mhshajib/retroxl"
)
type Payment struct {
AccountNo string
Amount float64
Reference string
}
func main() {
items := []Payment{
{"1234567890", 1200.50, "Invoice-001"},
{"9876543210", 300.00, "Invoice-002"},
}
headers := []string{"AccountNo", "Amount", "Reference"}
var rows [][]any
for _, p := range items {
rows = append(rows, []any{p.AccountNo, p.Amount, p.Reference})
}
sheet := retroxl.FromRows("BankUpload", headers, rows)
_ = retroxl.WriteXLSFile("bank_upload.xls", []retroxl.Sheet{sheet})
}
Example 2: Generate .xls from a slice without structs
package main
import (
"github.com/mhshajib/retroxl"
)
func main() {
headers := []string{"AccountNo", "Amount", "Reference"}
rows := [][]any{
{"1234567890", 1200.50, "Invoice-001"},
{"9876543210", 300.00, "Invoice-002"},
}
sheet := retroxl.FromRows("BankUpload", headers, rows)
_ = retroxl.WriteXLSFile("bank_upload.xls", []retroxl.Sheet{sheet})
}
Example 3: Stream .xls over HTTP
func handler(w http.ResponseWriter, r *http.Request) {
headers := []string{"AccountNo", "Amount"}
rows := [][]any{{"123", 100}, {"456", 200}}
sheet := retroxl.FromRows("Demo", headers, rows)
w.Header().Set("Content-Type", "application/vnd.ms-excel")
w.Header().Set("Content-Disposition", `attachment; filename="demo.xls"`)
_ = retroxl.WriteXLSWriter(w, []retroxl.Sheet{sheet})
}
When To Use RetroXL
Use RetroXL when:
- A bank or government portal only accepts
.xls - Your Go app generates
.xlsxor.csv - You want a container-friendly, dependency-free solution
- You need direct streaming over HTTP, gRPC, or cloud storage
Avoid RetroXL if you need:
- Complex styling
- Formulas or pivot tables
- BIFF8 binary (.xls) output
Conclusion
RetroXL provides a clean, dependency-free way to generate legacy .xls files directly from Go. If your workflows involve banking portals, finance systems, or older enterprise applications, RetroXL removes the complexity of external conversion tools.
Repository:
https://github.com/mhshajib/retroxl
Feedback and contributions are welcome.
Top comments (0)