Skip to main content

oxlsx

A high-performance Rust library for reading and writing .xlsx files with full formatting support — designed as an openpyxl equivalent in Rust, with Python bindings (PyO3).

5–10x faster than openpyxl on write operations. Streaming reads via open_readonly().

Status

v0.2.0 — phases 1–14 plus temporal values, style API additions, and Python type stubs. See PyPI and crates.io for published packages.

Phase Scope Status
1 ZIP parsing, styles, cell.font() / cell.fill() ✅ Done
2 Shared strings, Value::String/Bool, cell.text() ✅ Done
3 Multi-sheet, Value::Date, openpyxl API parity ✅ Done
4 Write support — Workbook::new() + wb.save() ✅ Done
5 Lazy / streaming reader (open_readonly()) ✅ Done
6 PyO3 Python bindings ✅ Done
7 Read-modify-write (open + edit + save) ✅ Done
8 Extended write features ✅ Done
9 Worksheet iteration API ✅ Done
10 Bulk write (append, insert/delete rows/cols, move_range) ✅ Done
11 Rich formatting ✅ Done
12 Hyperlinks + plain-text comments ✅ Done
13 Freeze panes, print settings, named ranges, tables (lossless RMW) ✅ Done
14 Distribution — abi3 wheels, PyPI + crates.io, release CI ✅ Done
15 Pandas engine / tabular adapter 🔲 Planned

openpyxl-migration additions (v0.1.1): conditional formatting (FormulaRule / CellIsRule), sheet auto_filter, PatternFill, get_column_letter / column_index_from_string, and dataframe_to_rows.

New in v0.2.0

  • Typed datetime, time, and duration values, including Python datetime, time, and timedelta support.
  • Strikethrough fonts, theme-color tint preservation, fill-color aliases, and cell row/column accessors.
  • Python type stubs for editor autocomplete and static analysis.
  • Atomic workbook saves with symbolic-link destination rejection.

Rust migration from v0.1.1: this release includes source-breaking API changes:

  • Replace Color::Theme(index) with Color::Theme(index, None) (or Some(tint)); update matching patterns to accept the second field.
  • Include the new Font.strike field in complete struct literals, or use ..Default::default().
  • Handle Value::DateTime, Value::Time, and Value::Duration in exhaustive matches.

The pandas engine remains planned; pandas Styler support is not included.

Install

Rust

# Cargo.toml
[dependencies]
oxlsx = "0.2.0"

Or pin to a tag from git: oxlsx = { git = "https://github.com/alvaroroco/oxlsx", tag = "v0.2.0" }

Python (build from source via maturin)

Prebuilt abi3 wheels (a single cp38-abi3 wheel per platform, runs on CPython 3.8+) are published to PyPI on each release:

pip install oxlsx

To build from source, use maturin. The wheel build (abi3 extension-module) is configured in pyproject.toml, so no extra flags are needed:

pip install maturin
maturin develop --release   # install into the active venv
# or: maturin build --release  -> target/wheels/oxlsx-0.2.0-cp38-abi3-*.whl

Contributors running the in-crate PyO3 tests use the interpreter-linked path instead: cargo test --features python.

Usage

Reading

use oxlsx::{Workbook, Value};

fn main() -> Result<(), oxlsx::OxlsxError> {
    let wb = Workbook::open("file.xlsx")?;

    // Access by index or name
    let ws = wb.sheet(0).unwrap();
    let ws = wb.sheet_by_name("Employees").unwrap();

    println!("Sheets: {:?}", wb.sheetnames());
    println!("Active: {}", wb.active().unwrap().title());

    if let Some(cell) = ws.cell("A1") {
        match cell.value() {
            Value::String(s)  => println!("text: {s}"),
            Value::Number(n)  => println!("number: {n}"),
            Value::Bool(b)    => println!("bool: {b}"),
            Value::Date(d)    => println!("date: {d}"),
            Value::DateTime(d) => println!("datetime: {d}"),
            Value::Time(t)    => println!("time: {t}"),
            Value::Duration(d) => println!("duration: {d}"),
            Value::Formula(f) => println!("formula: {f}"),
            Value::Empty      => println!("empty"),
        }

        if let Some(font) = cell.font() {
            println!("bold: {}", font.bold);
        }
        if let Some(fill) = cell.fill() {
            println!("fill: {}", fill.pattern_type);
        }
    }

    Ok(())
}

Writing

use oxlsx::{Color, Fill, Font, Workbook};

fn main() -> Result<(), oxlsx::OxlsxError> {
    let mut wb = Workbook::new();

    let ws = wb.active_mut().unwrap();
    ws.set_cell("A1", "Hello");
    ws.set_cell("B1", 42.0_f64);
    ws.set_cell("C1", true);
    ws.set_cell_font("A1", Font { bold: true, ..Default::default() });
    ws.set_cell_fill("A1", Fill {
        pattern_type: "solid".to_string(),
        fg_color: Some(Color::Argb("FFFF00".to_string())),
        bg_color: None,
    });

    wb.create_sheet("Data").set_cell("A1", "Sheet 2");

    wb.save("output.xlsx")?;
    Ok(())
}

API (openpyxl parity)

openpyxl (Python) oxlsx (Rust)
load_workbook("f.xlsx") Workbook::open("f.xlsx")
Workbook() Workbook::new()
wb.worksheets wb.worksheets()
wb.sheetnames wb.sheetnames()
wb.active wb.active() / wb.active_mut()
wb["Sheet1"] wb.sheet_by_name("Sheet1")
ws.title ws.title()
ws["A1"] ws.cell("A1")
ws["A1"] = value ws.set_cell("A1", value)
cell.value → str/int/float/date cell.value() → &Value
cell.font.bold cell.font()?.bold
cell.fill.fgColor cell.fill()?.fg_color
cell.comment cell.comment() / ws.set_cell_comment(...)
cell.hyperlink cell.hyperlink() / ws.set_cell_hyperlink(...)
ws.freeze_panes ws.freeze_panes() / ws.set_freeze_panes(...)
ws.print_area ws.print_area() / ws.set_print_area(...)
ws.print_title_rows / print_title_cols ws.print_title_rows() / ws.print_title_cols() (+ setters)
wb.defined_names wb.defined_names() (global) / ws.defined_names() (sheet-local)
ws.add_table(Table(...)) ws.add_table(Table::new(...))
ws.tables / del ws.tables["T"] ws.tables() / ws.remove_table("T")
ws.append([...]) / ws.append({...}) ws.append([...]) (Python also accepts a dict: column letters or 1-based ints)
ws.insert_rows/delete_rows/insert_cols/delete_cols(idx, amount) same names + signatures (1-based idx, amount=1)
ws.move_range(range, rows, cols, translate) ws.move_range(range, rows, cols, translate) (translate=True → NotImplementedError)
ws.auto_filter.ref = "A1:D10" ws.auto_filter().ref / Python ws.auto_filter.ref = ...
cell.fill = PatternFill(...) PatternFill(...) + ws.set_cell_fill(coord, fill)
ws.conditional_formatting.add(range, FormulaRule(...)) same (Python); FormulaRule, CellIsRule
get_column_letter(i) / column_index_from_string(s) oxlsx.get_column_letter(i) / oxlsx.column_index_from_string(s)
dataframe_to_rows(df, index, header) oxlsx.dataframe_to_rows(df, index, header)
wb.save("out.xlsx") wb.save("out.xlsx")

Benchmarks

Measured against openpyxl 3.1.2 on a 10k-row file (5 columns, mixed types). Absolute numbers are machine-dependent; the ratios are what matter. Reproduce with cargo bench + python3 benches/openpyxl_comparison.py.

Write

Operation openpyxl oxlsx Speedup
Single cell 1.90ms 0.18ms ~10.8x
1k rows × 5 cols 23.2ms 3.68ms ~6.3x
10k rows × 5 cols (50k cells) 222.4ms 54.1ms ~4.1x
3 sheets × 1k rows 35.9ms 6.80ms ~5.3x

Read (10k rows, 5 cols)

Operation openpyxl oxlsx Speedup
Metadata only (read_only / open_readonly()) 1.47ms 0.072ms ~20x
Open + iterate all cells 151ms (read_only) / 216ms (full) 33.4ms ~4.5–6.5x
Eager open() metadata, 10k 1.47ms 28ms tradeoff: eager loads the whole sheet for random access — use open_readonly() for streaming metadata

For metadata or large-file streaming, use open_readonly() (lazy, ~20x faster than openpyxl). Eager open() loads the full sheet to enable random ws.cell("A1") access.

Run benchmarks yourself:

cargo bench                          # Rust (criterion)
python3 benches/openpyxl_comparison.py  # Python baseline

Data Model

Type Description
Workbook Entry point — open or create, access sheets
Worksheet Access / set cells by Excel reference ("A1", "B3")
Cell Holds Value + StyleId + shared Arc<StyleSheet>
Value Empty | String(String) | Number(f64) | Bool(bool) | Date(NaiveDate) | DateTime(NaiveDateTime) | Time(NaiveTime) | Duration(chrono::Duration) | Formula(String)
StyleSheet Registry mapping StyleId → Font + Fill
StyleId Newtype (usize) — prevents index confusion at compile time
Font Bold, italic, strikethrough, size, family, color
Fill Pattern type, foreground/background color
Color Argb(String) | Indexed(u32) | Theme(u32, Option<f64>) (theme index and optional tint)
DefinedName Named range — name, value (+ comment, hidden); global or sheet-local
Table Worksheet table — display_name, ref, columns, optional TableStyleInfo; lossless RMW for unmodeled features
Dxf Differential format (fill / font) referenced by conditional-formatting rules
ConditionalFormatting Per-sqref rules — FormulaRule (expression) / CellIsRule (operator); unmodeled rules (color scales, etc.) preserved verbatim on RMW

Architecture

  • SAX only — quick-xml event loop, no DOM, no full-file loading
  • ZIP direct — reads from the container, no temp disk extraction
  • Arc<StyleSheet> — shared across Workbook → Worksheet → Cell, no lifetime params
  • PyO3-ready — all public structs are 'static compatible
  • Newtype pattern — StyleId(usize) prevents index confusion at compile time

Dependencies

Crate Role
quick-xml SAX XML parser + writer
zip OOXML container (deflate only, minimal wheel size)
thiserror Typed error enum
chrono Date resolution (NaiveDate)

Roadmap

✅ Done

  • ZIP container reading + SAX parsing
  • Styles: fonts, fills, colors, StyleId newtype
  • Shared strings → Value::String
  • Value::Bool, Value::Date (resolved at parse time)
  • Multi-sheet: wb.sheetnames(), wb.sheet_by_name(), ws.title()
  • Write support: Workbook::new(), ws.set_cell(), wb.save()
  • Style round-trip: bold font + solid fill survive write → open cycle
  • Criterion benchmarks + openpyxl comparison script
  • Streaming reader (open_readonly()), PyO3 bindings, read-modify-write
  • Extended write: formulas, merged cells, column widths, date numFmt
  • Iteration API (iter_rows/iter_cols/rows/columns/values, bounds)
  • Bulk write (append, insert/delete rows/cols, move_range) — Rust + Python; merges/dims shift, table-intersection guard, formulas verbatim
  • Rich formatting (number_format, alignment, border, protection)
  • Hyperlinks + plain-text comments
  • Freeze panes; print area / print titles
  • Named ranges — wb.defined_names() (global) + ws.defined_names() (sheet-local), conservative lossless passthrough
  • Tables — ws.add_table(), ws.tables, TableStyleInfo; lossless RMW (unmodeled table features preserved verbatim when untouched)
  • Conditional formatting — FormulaRule / CellIsRule + Dxf; RMW-preserving (unmodeled rules kept verbatim)
  • Sheet auto_filter; PatternFill + ws.set_cell_fill
  • Utils — get_column_letter, column_index_from_string, dataframe_to_rows
  • Distribution — abi3 wheels (CPython 3.8+), PyPI + crates.io, OIDC trusted publishing, release CI

🔲 Planned

  • Phase 15 — Pandas engine / tabular adapter
    • engine="oxlsx" integration via a dedicated adapter
    • DataFrame ↔ XLSX mapping, headers/index handling, and type conversions
    • Explicit adapter instead of monkeypatching by default

License

Licensed under either of:

at your option.

Release files for oxlsx 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oxlsx 0.2.0
File Size Uploaded
oxlsx-0.2.0.tar.gz 367.5 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for oxlsx 0.2.0
File
oxlsx-0.2.0-cp38-abi3-win_amd64.whl CPython 3.8 abi3 Windows x86-64 Details
oxlsx-0.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.8 abi3 Linux glibc 2.17+ x86-64 Details
oxlsx-0.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 abi3 Linux glibc 2.17+ ARM64 Details
oxlsx-0.2.0-cp38-abi3-macosx_11_0_arm64.whl CPython 3.8 abi3 macOS 11.0+ ARM64 Details
oxlsx-0.2.0-cp38-abi3-macosx_10_12_x86_64.whl CPython 3.8 abi3 macOS 10.12+ x86-64 Details

Total release size: 4.5 MB

Release files / oxlsx-0.2.0.tar.gz

Download URL oxlsx-0.2.0.tar.gz
Size 367.5 kB
Tags Source
SHA-256 checksum
How to use checksums
0aab2d79be09eec72ba05eaadb2d08d825f1c70756a56187c2ba4e2eb136b94f
BLAKE2b-256 checksum
How to use checksums
b94539e23ca11b5db5aaa3c99866b7c206feab073c799eb6a46d842d88c0f3bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / oxlsx-0.2.0-cp38-abi3-win_amd64.whl

Download URL oxlsx-0.2.0-cp38-abi3-win_amd64.whl
Size 715.7 kB
Tags CPython 3.8 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
faf9b4ce59135bf974bd06297d0eff1f1702a129c9841d60a69b0b89607d8074
BLAKE2b-256 checksum
How to use checksums
aaaffc9928a616590b1d276ccebfc5eaddc7564fce7f8a6912d057cba0b1c5fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / oxlsx-0.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL oxlsx-0.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 884.1 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
831b7487a452328f5a550d265421388e59014e76a77da016f11966df43e05856
BLAKE2b-256 checksum
How to use checksums
997da00320fe50bc1c71a0e5feeb118a57ae5843b13d49563e698ffebbe275d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / oxlsx-0.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL oxlsx-0.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 885.5 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
f851e3592e25bb82fc185df5617050da427110a93ffb773763d7f5dd0a61a41b
BLAKE2b-256 checksum
How to use checksums
eedff3943c53439a7ebd1e6f067ade5ab4b825c8562cf334764dc6fb2f7586ca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / oxlsx-0.2.0-cp38-abi3-macosx_11_0_arm64.whl

Download URL oxlsx-0.2.0-cp38-abi3-macosx_11_0_arm64.whl
Size 803.5 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
981757d2732dcd5d3c99b7914ab0f9ce8f15b052c31232342ca28ebb83f2e924
BLAKE2b-256 checksum
How to use checksums
23c4281959c4070b964e0d06a74711148acf2e72e6417ad5a16d0ae58f956e8f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / oxlsx-0.2.0-cp38-abi3-macosx_10_12_x86_64.whl

Download URL oxlsx-0.2.0-cp38-abi3-macosx_10_12_x86_64.whl
Size 818.0 kB
Tags CPython 3.8 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
01597bdb7ada731c542c1e2951b60f772fd7cc52b956ff6b993a18f804cf3e13
BLAKE2b-256 checksum
How to use checksums
d7e892d0df3014eef79cde955e9379a57ebf906ac658232f1bee7773bfac66ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

6 release files

0.1.1

6 release files

0.1.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page