Skip to main content
Jetxl reads Arrow arrays directly, so what lands in a cell depends on the Arrow type of the column rather than on the Python object you started with.

Supported types

Types Jetxl converts for you

Three common cases would otherwise be rejected, so Jetxl normalizes them once per batch before writing.
A pandas Categorical, or a Polars Categorical or Enum, arrives as an Arrow Dictionary array. Jetxl decodes it to the underlying value type, usually Utf8, and writes it as text.
Utf8View and BinaryView are cast to Utf8 and Binary. You meet these when you pass a Polars DataFrame straight in rather than calling .to_arrow().
Columns that are neither dictionary-encoded nor view types pass through untouched, so PyArrow and pandas workloads pay only a cheap scan.
Because view types are handled, a bare Polars DataFrame works without conversion:
The examples on this site call .to_arrow() explicitly, which is clearer about what’s being passed and works identically for pandas.

Unsupported types

Jetxl checks the schema once per sheet, before writing anything, and raises an OSError naming the offending column:
This is a catchable error rather than a silent empty column, and the check costs nothing measurable because it runs per column rather than per cell. Nested types are the usual cause. A struct, list or map column has no single-cell representation in a spreadsheet, so flatten or serialize it first:

Values that change on the way out

Three conversions happen silently. None of them raises, so the file writes successfully and the difference only shows when you open it.
NaN and infinity become empty cells. Neither is a valid numeric value in the spreadsheet format, and writing one makes the whole workbook unreadable, so Jetxl writes an empty cell instead. This applies on both the Arrow and dictionary paths.If a NaN in your data means something, convert it before writing:
Control characters are stripped from text. Bytes in the C0 control range, other than tab, newline and carriage return, have no legal representation in the underlying XML at all. Jetxl drops them rather than producing a file Excel rejects. This matches XlsxWriter’s behavior.Text arriving from scraped HTML or legacy systems is where this shows up.
Nulls become empty cells, not the text "None", "NaN" or "null". An empty cell is not zero: Excel’s AVERAGE skips empty cells but includes zeros, so a null-heavy column averages differently depending on whether you filled the gaps before writing.

Grid limits

Excel’s own ceiling is 1,048,576 rows and 16,384 columns. Jetxl checks both before writing and raises rather than producing a truncated file.
The row limit counts the header. A frame of exactly 1,048,576 rows written with write_header_row=True is one row over and fails. Set write_header_row=False, or split the export.
For data beyond the limit, split across sheets with write_sheets_arrow, or reconsider whether a spreadsheet is the right format. At that size a CSV or Parquet file serves most readers better.

Dates

Date32 counts days from 1 January 1970, and Jetxl converts that to the serial number Excel expects. The cell holds a real date, not text, so Excel sorts and filters it correctly. Excel’s date system includes a leap day, 29 February 1900, that never existed. Jetxl accounts for it, so dates before 1 March 1900 land on the right day rather than one day out. Historical data is safe. A date still needs a format to display readably. Without one you see the underlying serial number:
The built-in date name maps to Excel’s short-date format, which renders by the reader’s locale. Pass the custom code when you need a fixed appearance. See Number formats for the full list.