Data formats
Who this is for: people and AI assistants who read or write XML, CSV, NDJSON, Excel workbooks, fixed-width records, form data, multipart bodies, base64, hex or older text encodings in a workflow.
Before you start: know where data formats show up: ctx.formats in scripts, the xml.parse, xml.build, xml.split, csv.parse, xlsx.read, xlsx.write, fixed.parse and fixed.build actions, and the parse option of a webhook trigger.
This page lists every data format operation, the options it takes, its default limits and the errors it can give. It is generated from the code, so it always matches what Liitora does. AI assistants can read the same list as the MCP resource liitora://reference/data-formats.
One reader everywhere
Section titled “One reader everywhere”Scripts, built-in actions and webhook triggers all use the same readers and writers. The same data gives the same result in a run: script, in platform/xml.parse and in a webhook with parse: xml.
Data from outside is read inside the sandbox, never by the Liitora worker itself. Each piece of data gets its own short-lived sandbox with no network, no files and no access to your secrets. It has its own memory, CPU and time limits. If data from one sender tries to attack the reader, it can’t reach anything else.
The readers are strict. They refuse data that two systems could read in different ways, such as XML with a DOCTYPE, a CSV row with a missing field or text that is not valid in its encoding. Refusing is safer than guessing.
Excel workbooks
Section titled “Excel workbooks”Liitora reads and writes .xlsx workbooks. It never reads macro workbooks (.xlsm), binary workbooks (.xlsb) or the old .xls format, so a file that could carry code is refused instead of opened.
- Formulas are never run. A formula cell gives the value Excel saved with it the last time the file was saved, and the result’s
formulassays how many formula cells there were. If a value looks stale, open and save the file in Excel, or compute the value in the workflow instead. - Text that looks like a formula is written as text. When Liitora writes a workbook, a cell that starts with
=,+,-,@, a tab or a carriage return (also after leading spaces, and the full-width signs such as=) gets a leading', the same rule as for CSV files. This stops a value from your data from running as a formula when someone opens the file. Numbers are never changed. - Dates follow the workbook. Excel stores dates as numbers counted from 1900 or, in some older Mac files, from 1904. Liitora reads which one the file uses (the result’s
epoch) and turns date cells into ISO 8601 text such as2026-10-04or2026-10-04T08:30:00. Setdates: "serial"to get the numbers instead. - Zip bombs are refused. A workbook is a zip file. Liitora limits how many files it holds, how large they unpack and how much one file may grow when unpacked, and refuses a workbook that holds another archive.
Fixed-width records
Section titled “Fixed-width records”Fixed-width files, common for bank and payroll exchanges, give every field a set position and width on each line. You describe the layout as a list of fields and say with unit whether positions count characters ("chars") or bytes in the file’s encoding ("bytes"). Use "bytes" when the file’s specification counts bytes and the file has letters such as ä or ö that take more than one byte.
Liitora never cuts or guesses silently. Overlapping fields are refused. A value too long for its field fails the step instead of being cut. A line shorter or longer than the layout fails too, unless you ask for padding (short_lines in a step, shortLines in a script) or for the extra to be dropped (long_lines or longLines). Empty lines are skipped.
Errors
Section titled “Errors”When data can’t be read or written:
- In a script, the call throws an
ErrorwithnameFormatErrorandcodeFORMAT_ERROR. The message says what is wrong (see Error kinds) and usually names the line. Catch it to handle bad data yourself. - In a built-in action or a webhook trigger, and in a script that doesn’t catch the error, the step fails with
format_error.
A format_error is never retried, whatever the step’s retry says, because the same data would fail the same way. Fix the data where it comes from, then run the workflow again.
If the sandbox that reads the data can’t start, the step fails with codec_unavailable. That is a problem on our side, not in your data. A step whose retry includes network tries again.
Limits
Section titled “Limits”The limits below are the defaults for scripts. Built-in actions can have their own, stricter or larger, limits, listed with each action in Built-in actions. For example, csv.parse reads up to 50 MB. A built-in step that needs more memory, CPU or time than it is allowed fails with format_error too, with a message that says so.
Operations
Section titled “Operations”xml.parse
Section titled “xml.parse”ctx.formats.xml.parse(text, options?)
- Takes: XML text
- Gives: an object with one key, the root element
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
arrays |
"always" | string[] |
only repeated elements | Element paths from the root (such as “order.items.item”) that are always arrays, or “always” for every child element. |
namespaces |
"strip" | "prefix" | "uri" |
“strip” | How namespaced names appear: local names only, names as written, or {uri}local. |
charset |
string |
“utf-8” | The charset the text was decoded from. An encoding in the XML declaration must match it. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_depth |
64 | Deepest element nesting. |
max_nodes |
200,000 | Most elements, attributes and text pieces. |
max_attributes |
256 | Most attributes on one element. |
max_name_bytes |
1,024 | Longest element or attribute name, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
xml.build
Section titled “xml.build”ctx.formats.xml.build(value, options?)
- Takes: an object with one key, the root element
- Gives: XML text
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
declaration |
boolean |
true | Write the XML declaration first. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_depth |
64 | Deepest element nesting. |
max_nodes |
200,000 | Most elements, attributes and text pieces. |
max_attributes |
256 | Most attributes on one element. |
max_name_bytes |
1,024 | Longest element or attribute name, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
csv.parse
Section titled “csv.parse”ctx.formats.csv.parse(text, options?)
- Takes: CSV text or a Uint8Array of UTF-8
- Gives: an array of objects, or of arrays without a header
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
delimiter |
string |
“,” | One ASCII character between fields. |
quote |
string |
‘“’ | One ASCII character that quotes a field. |
header |
boolean |
true | The first row names the columns and every row becomes an object. Otherwise every row is an array of strings. |
trim |
boolean |
false | Remove spaces and tabs around every field and header name. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_rows |
200,000 | Most data rows. |
max_columns |
2,000 | Most fields in one row. |
max_field_bytes |
1 MB | Largest single field, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
csv.build
Section titled “csv.build”ctx.formats.csv.build(rows, options?)
- Takes: an array of objects, or of arrays without a header
- Gives: CSV text
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
delimiter |
string |
“,” | One ASCII character between fields. |
quote |
string |
‘“’ | One ASCII character that quotes a field. |
header |
boolean |
true | Rows are objects and a header row of the first row’s keys is written first. Otherwise rows are arrays. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_rows |
200,000 | Most data rows. |
max_columns |
2,000 | Most fields in one row. |
max_field_bytes |
1 MB | Largest single field, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
ndjson.parse
Section titled “ndjson.parse”ctx.formats.ndjson.parse(text)
- Takes: NDJSON text or a Uint8Array of UTF-8
- Gives: an array with one value per line
Options: none.
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_lines |
200,000 | Most lines. |
max_line_bytes |
1 MB | Longest line, in bytes. |
max_depth |
64 | Deepest nesting in one line. |
max_output_bytes |
16 MB | Largest result, in bytes. |
ndjson.build
Section titled “ndjson.build”ctx.formats.ndjson.build(values)
- Takes: an array of values
- Gives: NDJSON text, one line per value
Options: none.
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_lines |
200,000 | Most lines. |
max_line_bytes |
1 MB | Longest line, in bytes. |
max_depth |
64 | Deepest nesting in one line. |
max_output_bytes |
16 MB | Largest result, in bytes. |
form.parse
Section titled “form.parse”ctx.formats.form.parse(text, options?)
- Takes: application/x-www-form-urlencoded text or a Uint8Array
- Gives: an object of strings, with arrays for repeated fields
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
duplicates |
"array" | "error" |
“array” | What a repeated field does: its values become an array, or the parse fails. |
arrays |
"always" | string[] |
only repeated fields | Field names that are always arrays, or “always” for every field. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_fields |
10,000 | Most fields. |
max_name_bytes |
1,024 | Longest field name, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
form.build
Section titled “form.build”ctx.formats.form.build(fields)
- Takes: an object of strings, numbers, booleans and arrays of those
- Gives: application/x-www-form-urlencoded text
Options: none.
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
8 MB | Largest input, in bytes. |
max_fields |
10,000 | Most fields. |
max_name_bytes |
1,024 | Longest field name, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
multipart.parse
Section titled “multipart.parse”ctx.formats.multipart.parse(body, options)
- Takes: a multipart/form-data body as a Uint8Array or text
- Gives: an array of parts: name, filename, contentType, headers, data
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
boundary |
string |
required | The boundary that separates the parts. Give this or contentType. |
contentType |
string |
required | The whole Content-Type header, such as multipart/form-data; boundary=x. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_parts |
1,000 | Most parts. |
max_headers |
16 | Most header lines in one part. |
max_header_bytes |
8,192 | Largest header block of one part, in bytes. |
max_name_bytes |
1,024 | Longest part name or filename, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
multipart.build
Section titled “multipart.build”ctx.formats.multipart.build(parts, options?)
- Takes: an array of parts: name, filename, contentType, data
- Gives: { body, contentType }
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
boundary |
string |
picked from the content | The boundary to use. It must not appear in any part’s data. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_parts |
1,000 | Most parts. |
max_headers |
16 | Most header lines in one part. |
max_header_bytes |
8,192 | Largest header block of one part, in bytes. |
max_name_bytes |
1,024 | Longest part name or filename, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
text.decode
Section titled “text.decode”ctx.formats.text.decode(bytes, options)
- Takes: a Uint8Array
- Gives: text, or { text, replaced } with onInvalid: “replace”
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
encoding |
string |
required | Required. One of utf-8, utf-16le, utf-16be, windows-1252, iso-8859-1, iso-8859-15. |
onInvalid |
"error" | "replace" |
“error” | What a malformed byte sequence does: fail, or become U+FFFD with a count in the result. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
text.encode
Section titled “text.encode”ctx.formats.text.encode(text, options)
- Takes: text
- Gives: a Uint8Array
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
encoding |
string |
required | Required. One of utf-8, utf-16le, utf-16be, windows-1252, iso-8859-1, iso-8859-15. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
base64.encode
Section titled “base64.encode”ctx.formats.base64.encode(data, options?)
- Takes: text (as UTF-8) or a Uint8Array
- Gives: padded base64 text
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
url |
boolean |
false | Use the URL-safe alphabet (- and _ instead of + and /). |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
base64.decode
Section titled “base64.decode”ctx.formats.base64.decode(text, options?)
- Takes: base64 text
- Gives: a Uint8Array
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
url |
boolean |
false | Use the URL-safe alphabet (- and _ instead of + and /). |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
hex.encode
Section titled “hex.encode”ctx.formats.hex.encode(data)
- Takes: text (as UTF-8) or a Uint8Array
- Gives: lower-case hex text
Options: none.
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
hex.decode
Section titled “hex.decode”ctx.formats.hex.decode(text)
- Takes: hex text
- Gives: a Uint8Array
Options: none.
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_output_bytes |
16 MB | Largest result, in bytes. |
xlsx.read
Section titled “xlsx.read”ctx.formats.xlsx.read(bytes, options?)
- Takes: a Uint8Array with an .xlsx workbook
- Gives: { sheet, sheets, epoch, formulas, rows }
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
sheet |
string | number |
the first sheet | The sheet to read: its name (an exact match first, then ignoring case) or its 0-based number. |
header |
boolean |
true | Row 1 names the columns and every later row becomes an object. Otherwise every row is an array. |
dates |
"iso" | "serial" |
“iso” | Numbers with a date or time format become ISO 8601 text in the workbook’s date system, or stay serial numbers. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest workbook file, in bytes. |
max_entries |
1,000 | Most files inside the zip package. |
max_uncompressed_bytes |
128 MB | Total unpacked size of the package, in bytes. |
max_entry_bytes |
64 MB | Unpacked size of one file in the package, in bytes. |
max_compression_ratio |
200 | Highest unpacked to packed size ratio of one file over 64 KiB. |
max_sheets |
256 | Most sheets. |
max_rows |
200,000 | Most rows, the header included. |
max_columns |
2,000 | Most columns. |
max_cells |
2,000,000 | Most cells in the rows read or written. |
max_shared_strings |
500,000 | Most entries in the shared string table. |
max_styles |
64,000 | Most cell formats, and most custom number formats. |
max_cell_bytes |
1 MB | Longest text of one cell, in bytes. |
max_output_bytes |
32 MB | Largest result: cell text for a read, the file for a write, in bytes. |
xlsx.write
Section titled “xlsx.write”ctx.formats.xlsx.write(rows, options?)
- Takes: an array of rows, or an object of sheet name to rows
- Gives: a Uint8Array with an .xlsx workbook
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
sheet |
string |
“Sheet1” | The sheet name when the input is one array of rows. |
header |
boolean |
true | Rows are objects and the first row’s keys become a header row. Otherwise rows are arrays. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest workbook file, in bytes. |
max_entries |
1,000 | Most files inside the zip package. |
max_uncompressed_bytes |
128 MB | Total unpacked size of the package, in bytes. |
max_entry_bytes |
64 MB | Unpacked size of one file in the package, in bytes. |
max_compression_ratio |
200 | Highest unpacked to packed size ratio of one file over 64 KiB. |
max_sheets |
256 | Most sheets. |
max_rows |
200,000 | Most rows, the header included. |
max_columns |
2,000 | Most columns. |
max_cells |
2,000,000 | Most cells in the rows read or written. |
max_shared_strings |
500,000 | Most entries in the shared string table. |
max_styles |
64,000 | Most cell formats, and most custom number formats. |
max_cell_bytes |
1 MB | Longest text of one cell, in bytes. |
max_output_bytes |
32 MB | Largest result: cell text for a read, the file for a write, in bytes. |
fixed.parse
Section titled “fixed.parse”ctx.formats.fixed.parse(data, options)
- Takes: text or a Uint8Array
- Gives: an array of objects with string values
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
fields |
{ name, start?, width }[] |
required | The layout. start is 1-based and defaults to right after the previous field; gaps are allowed, overlaps are refused. |
unit |
"chars" | "bytes" |
required | What start and width count: characters of the text, or bytes in the encoding. |
encoding |
string |
“utf-8” | The charset of bytes in, or of the bytes that unit: “bytes” counts. UTF-16 only works with unit: “chars”. |
trim |
"both" | "left" | "right" | "none" |
“both” | Which padding spaces to remove from every field. A field can set its own trim. |
shortLines |
"error" | "pad" |
“error” | A line shorter than the record is refused, or read as if padded with spaces. |
longLines |
"error" | "ignore" |
“error” | A line longer than the record is refused, or its extra characters are dropped on purpose. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_rows |
200,000 | Most records (lines). |
max_fields |
1,000 | Most fields in the layout. |
max_line_length |
65,536 | Longest line and longest record, in the layout’s unit. |
max_output_bytes |
16 MB | Largest result, in bytes. |
fixed.build
Section titled “fixed.build”ctx.formats.fixed.build(rows, options)
- Takes: an array of objects
- Gives: text (unit: “chars”) or a Uint8Array (unit: “bytes”)
Options:
| Option | Type | Default | What it does |
|---|---|---|---|
fields |
{ name, start?, width, align?, pad? }[] |
required | The layout. align is “left” (the default) or “right”; pad is one character (a space by default). |
unit |
"chars" | "bytes" |
required | What start and width count: characters of the text, or bytes in the encoding. |
encoding |
string |
“utf-8” | unit: “bytes” only: the charset of the bytes written. |
lineEnding |
"\n" | "\r\n" |
“\n” | Written after every record. |
Limits:
| Limit | Default | What it limits |
|---|---|---|
max_input_bytes |
16 MB | Largest input, in bytes. |
max_rows |
200,000 | Most records (lines). |
max_fields |
1,000 | Most fields in the layout. |
max_line_length |
65,536 | Longest line and longest record, in the layout’s unit. |
max_output_bytes |
16 MB | Largest result, in bytes. |
Error kinds
Section titled “Error kinds”What can make a data format operation fail. The error’s message names the problem and, when it can, the line.
| Kind | What went wrong |
|---|---|
syntax |
The input is not well-formed. |
input_too_large |
The input is larger than its limit. |
output_too_large |
The result would be larger than its limit. |
too_deep |
Values are nested deeper than the depth limit. |
too_many_nodes |
The XML has more nodes than its limit. |
too_many_attributes |
An element has more attributes than its limit. |
doctype |
The XML has a DOCTYPE, which is never accepted. |
entity |
The XML uses an entity other than the five predefined ones. |
duplicate_attribute |
An element has the same attribute twice. |
bom |
A byte order mark where none belongs. |
encoding_mismatch |
The XML declaration names a different encoding than the text came in. |
namespace |
A namespace prefix is not declared, or a declaration is invalid. |
invalid_char |
A character XML does not allow. |
invalid_name |
A name that is not a valid XML name. |
invalid_value |
A value the builder cannot write. |
invalid_option |
An unknown option, or an option with the wrong type or value. |
too_many_rows |
More rows or lines than the limit. |
too_many_columns |
A row has more fields than the limit. |
ragged_row |
A row has a different number of fields than the header, or a fixed-width line has the wrong length. |
line_too_long |
An NDJSON or fixed-width line is longer than its limit. |
invalid_encoding |
The bytes are not valid in the named encoding. |
unmappable |
A character has no representation in the target encoding. |
invalid_encoded_text |
Base64 or hex text is malformed. |
duplicate_field |
A form field repeats where only one is allowed, or a multipart part repeats a header. |
too_many_fields |
More form fields, multipart parts or fixed-width fields than the limit. |
resource_limit |
The data needed more memory, CPU or time than a built-in step allows. |
archive |
The zip archive is damaged, unsupported, over a size or compression limit, or holds another archive. |
macro |
The workbook has macros or is a binary or legacy Excel file, which is never read. |
too_many_cells |
More cells, sheets, shared strings or cell styles than the limit. |
field_too_long |
A value is longer than its field or cell allows. |