File Actions
Sixteen operators over storage — moving files, reading their metadata, generating documents, and the closed spreadsheet subset.
File actions operate against the app's configured storage, whether that is the local filesystem or an object store.
Storage
| Operator | Props | Does |
|---|---|---|
upload |
source, path?, contentType? |
Uploads a file to storage |
download |
key |
Downloads a stored file |
delete |
key |
Deletes a stored file |
copy |
source, destination |
Copies a stored file |
move |
source, destination |
Moves or renames a file |
list |
prefix, limit? |
Lists files under a prefix |
Metadata and access
| Operator | Props | Does |
|---|---|---|
getMetadata |
key |
Reads size, content type and the rest |
signUrl |
key, expiresIn?, operation? |
Mints a time-limited URL for download or upload |
Generation
| Operator | Props | Does |
|---|---|---|
generatePdf |
template, filename, data?, pageSize?, orientation?, margins?, destination? |
Renders an HTML template to PDF |
generateCsv |
data, filename, columns?, delimiter?, includeHeaders?, destination? |
Writes a CSV from an array |
generateXlsx |
data? or sheets?, filename, columns?, sheetName?, destination? |
Writes a workbook from rows |
Parsing and transforming
| Operator | Props | Does |
|---|---|---|
parseCsv |
source?, key?, content?, columns?, skipRows?, delimiter? |
Parses CSV into rows |
parseXlsx |
source?, key?, sheet?, header?, range?, skipRows? |
Parses a worksheet into rows |
extractText |
source, format? |
Extracts text from a document |
transformImage |
source, width?, height?, fit?, format?, quality?, destination? |
Resizes or converts an image |
compress |
files, filename, destination? |
Zips several files into one archive |
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
name |
string | Step name for referencing outputs (e.g., "fetchUser"). Must be alphanumeric + underscore. | ||
label |
string | Human-readable label for this action step | ||
continueOnError |
boolean | false | Continue workflow even if this action fails (default: false) | |
timeout |
number | Per-action timeout in ms (1000-900000). Terminates the action when exceeded. | ||
type |
enum | file |
Constant value 'file' for type discrimination in discriminated unions | |
operator |
enum | upload, download, delete, copy, move, list, getMetadata, signUrl, generateCsv, generatePdf, parseCsv, extractText, transformImage, compress, parseXlsx, generateXlsx |
Selects the operation within the 'file' action family; it decides which props the step takes |
retry
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
retry |
object | Automatic retry behavior for failed executions with fixed or exponential backoff | ||
retry.maxAttempts |
number | Maximum retry attempts (1-10) | ||
retry.delayMs |
number | Base delay between retries in milliseconds (100-60000, default: 1000) | ||
retry.strategy |
enum | fixed, exponential |
Retry strategy: fixed delay or exponential backoff (default: fixed) |
props
| Path | Kind | Values | Default | Description |
|---|---|---|---|---|
props |
object | |||
props.source |
string | |||
props.path |
string | Storage key destination. If omitted, auto-generated. | ||
props.contentType |
string | |||
props.key |
string | |||
props.sourceKey |
string | |||
props.destinationKey |
string | |||
props.prefix |
string | Storage key prefix to list files under | ||
props.limit |
number | Maximum number of files to return | ||
props.expiresIn |
number | URL expiration time in seconds (default: 3600) | ||
props.operation |
enum | download, upload, resize, convert |
||
props.data |
string | object | |||
props.filename |
string | |||
props.columns |
array | |||
props.columns[] |
object | |||
props.columns[].key |
string | |||
props.columns[].field |
string | Object key to extract as column value | ||
props.columns[].header |
string | |||
props.delimiter |
enum | ,, ;, , | |
||
props.includeHeaders |
boolean | Include column headers as first row (default: true) | ||
props.destination |
string | Storage key for the output file. If omitted, file is stored in temporary storage and auto-cleaned after STORAGE_TEMP_CLEANUP_AFTER (default: 24 hours). | ||
props.template |
string | HTML template for PDF content (supports template variables) | ||
props.pageSize |
enum | A4, A3, Letter, Legal |
Page size (default: A4) | |
props.orientation |
enum | portrait, landscape |
Page orientation (default: portrait) | |
props.margins |
object | Page margins | ||
props.margins.top |
string | Top margin (e.g., "1cm", "0.5in") | ||
props.margins.right |
string | Right margin | ||
props.margins.bottom |
string | Bottom margin | ||
props.margins.left |
string | Left margin | ||
props.content |
string | Inline CSV text to parse (alternative to source/key) |
||
props.columns[].name |
string | Object key for the parsed value | ||
props.columns[].index |
number | Zero-based CSV column index to read this value from | ||
props.skipRows |
number | |||
props.format |
enum | plain, markdown, jpeg, png, webp |
||
props.width |
number | Target width in pixels (1-2500) | ||
props.height |
number | Target height in pixels (1-2500) | ||
props.fit |
enum | fill, inside |
How a two-dimension resize uses the box: fill stretches to exactly width x height (distorts); inside scales down to fit within the box, preserving the aspect ratio (output may be smaller than requested). Required when both width and height are set; ignored otherwise. |
|
props.outputFormat |
enum | jpeg, png, webp |
Output image format (operation: convert) | |
props.quality |
number | Output quality for lossy formats (1-100, default: 80) | ||
props.keys |
array | Array of storage keys to compress into the archive | ||
props.keys[] |
string | String with {{step.property}} variables, {{helper args}} expressions, and $env.VAR references | ||
props.files |
string | Template resolving to array of storage keys to compress | ||
props.sheet |
string | number | Sheet to read: a sheet name, or a zero-based sheet index (default: first sheet) | ||
props.header |
boolean | Treat the first row as a header row — it becomes columns and is excluded from data (default: false) |
||
props.range |
string | A1-style range to read, e.g. "A1:C10" (default: the sheet's full used range) | ||
props.sheets |
array | Multi-sheet output, one entry per worksheet (alternative to data) |
||
props.sheets[] |
object | |||
props.sheets[].name |
string | Worksheet name as it appears on the tab | ||
props.sheets[].data |
string | Template variable referencing this sheet's rows | ||
props.sheets[].columns |
array (truncated) | Column definitions for this sheet | ||
props.sheetName |
string | Worksheet name for the single sheet (default: "Sheet1") |
CSV
parseCsv needs exactly one input: source (a storage key), key (its alias), or content — inline CSV text, which parses a webhook body or an earlier step's output without a storage round-trip. Omitting all three is a configuration error.
skipRows drops that many leading non-blank lines and nothing else, so it strips a preamble without changing the shape of the output: the first line that survives is still read as the header, and rows stay keyed by header name.
delimiter is one of comma, semicolon, tab or pipe. Omitted, it is auto-detected by counting candidates outside quoted fields in the first surviving line, so a semicolon-delimited export whose header legitimately contains a comma still reads correctly. Pass columns to map explicitly instead, each entry taking a name plus either a header name or a zero-based index.
Spreadsheets — a closed subset
parseXlsx and generateXlsx support a named, closed subset of the spreadsheet format rather than the format at large. On the way in that subset is: shared and inline strings, numbers, booleans, dates recognised through the cell's number format — an Excel date serial is otherwise indistinguishable from a plain number — and formulas read as their cached value, never evaluated. Dates come back as ISO 8601 strings rather than date objects, so they survive being persisted to run history and re-read by a template.
The wall is at data outside the cell grid
A workbook carrying a chart, a drawing, an embedded image, a pivot table or a macro is refused by name. Reading it would hand back the cells and quietly drop the part of the document its author cared about, which is the worst available outcome: a plausible answer that is missing the point of the file.
Refusal is detected on both the archive's part paths and its content-type overrides, because the two can disagree. Cosmetic styling — fonts, fills, borders — is not refused but ignored, since refusing it would refuse essentially every real workbook. Outside the subset the action fails loudly and deliberately: convert the file rather than trust a wrong answer.
Reading
parseXlsx takes the workbook as source or its alias key; a data URI and an https URL are also accepted. sheet selects a worksheet by name, or by zero-based position when given a number; omitted, the first sheet in the workbook's declared order is read. range restricts reading to an A1-style window, defaulting to the sheet's used range. header treats the first row as a header, promoting it to columns and excluding it from the data. skipRows then drops that many rows off the top of the grid.
The output carries the rows plus the sheet's name, every sheet name in the workbook, the row count and the columns. That list of names is what lets an automation discover a workbook's sheets on a first call and target one on a second.
- name: importSheet
type: file
operator: parseXlsx
props:
source: '{{trigger.data.key}}'
sheet: Orders
header: true
range: 'A1:F500'Writing
generateXlsx writes the minimal part set a consumer requires, adding a styles part only when a date cell is present. Pass data for a single sheet, naming it with sheetName, or sheets for several, each entry taking a name, its own data and optionally its own columns. columns selects and orders the fields and supplies their header labels.
The refusal here is per cell value: anything outside string, number, boolean and date — an object, an array, a bigint, a not-a-number, an invalid date — fails the step with an error naming the sheet and the cell reference, rather than being coerced into a plausible-looking string.
Round-tripping a generated workbook back through the parser is lossless within the subset, and only within it.
- name: exportOrders
type: file
operator: generateXlsx
props:
data: '{{fetchOrders.records}}'
filename: 'orders-{{trigger.data.month}}.xlsx'
sheetName: Orders
destination: exports/PDF and images
- name: invoice
type: file
operator: generatePdf
props:
template: invoice-template
filename: 'invoice-{{trigger.data.id}}.pdf'
data: '{{trigger.data}}'
pageSize: A4
orientation: portrait
destination: invoices/- name: thumbnail
type: file
operator: transformImage
props:
source: '{{upload.result.key}}'
width: 320
format: webp
quality: 70A conversion naming no format encodes to WebP, and a plain resize keeps the source format rather than transcoding it. That is the built-in behaviour rather than a setting — name a format on the action when a particular consumer needs another codec.
Behaviour
Compress Action
- Creates ZIP from array of storage keys
- With destination stores ZIP at specified key
- Without destination uses temp storage
- Result includes fileCount
- Nonexistent source key fails gracefully
Copy Action
- Copy creates duplicate at destination key
- Copy preserves original at source key
- Copy to existing key overwrites destination
Delete Action
- Delete by key removes file and metadata
- Delete nonexistent key returns error (respects continueOnError)
Download Action
- Download by key returns metadata and stores content in temp key
- Download nonexistent key returns error
- Download result usable as source in subsequent file action
Extract Text Action
- Extract from PDF returns plain text
- Markdown format returns structured markdown
- Result includes wordCount and pageCount
- Unsupported format returns error
Generate CSV Action
- File generateCsv creates CSV from data array with configurable column mapping
- GenerateCsv with destination stores file at specified storage key
- GenerateCsv without destination uses temp storage with auto-cleanup
- GenerateCsv result contains key, filename, contentType, size
- GenerateCsv with delimiter and includeHeaders=false
- GenerateCsv result key is usable as source in subsequent action
- A value containing the configured delimiter is quoted on disk, not split
- Delimiter-bearing output round-trips: parseCsv reads back the original value
Generate PDF Action
- File generatePdf creates PDF from HTML template with template variable substitution
- GeneratePdf with destination stores file at specified storage key
- GeneratePdf without destination uses temp storage with auto-cleanup
- GeneratePdf result contains key, filename, contentType, size
- GeneratePdf with pageSize, orientation, and margins
- GeneratePdf result key is usable as source in subsequent action
Generate XLSX Action
- Writes a real OOXML package to
destination— a ZIP container carrying the parts an.xlsxconsumer requires - Values survive a
generateXlsx→parseXlsxround-trip with their types intact, not flattened to strings - Honours an explicit
columnsselection and itsheaderlabels — an undeclared key never becomes a cell - The
sheets[]form emits every declared worksheet, under its declaredname, in declaration order
Get Metadata Action
- Returns file info without downloading content
- Nonexistent key returns error
List Action
- List by prefix returns matching file metadata array
- List with limit restricts result count
- List empty prefix returns empty array
Move Action
- Move relocates to destination and deletes source
- Move result contains new key
- Source key no longer exists after move
Parse CSV Action
- Reads CSV from storage key, returns structured data array
- Custom columns maps CSV columns to object keys
- skipRows skips header/metadata rows
- Custom delimiter parses semicolon/tab files
- Result data usable in subsequent record batchCreate
- A newline inside a quoted field stays one row (RFC 4180)
- Quoted whitespace is preserved; unquoted cells still trim
- A semicolon file whose header contains one stray comma is detected as semicolon-delimited
- Inline
contentparses with no storage round-trip - A
data:URI source parses with no storage round-trip - An
https://source is fetched over the network, then parsed - A windows-1252 export decodes its accented characters
- skipRows drops preamble lines and still yields header-keyed rows
Parse XLSX Action
- Reads a workbook from a storage key, typing each cell as a string, a number or a boolean rather than as text throughout
- Resolves cells written as inline strings, not only those pointing into the shared-string table
- A serial carrying a date number format reads as a date, while the same bare serial stays a number
- A formula cell yields the workbook's cached value — the reader never re-evaluates the formula
sheetgiven a name reads that worksheet rather than the firstsheetgiven a zero-based integer selects positionally- Reports every sheet name in workbook order, so a follow-up action can target one it discovered
- Refuses a workbook outside the supported OOXML subset by name, instead of misparsing it
- Refuses a file that is not a ZIP container at all
Sign URL Action
- Generates time-limited download URL
- Respects custom expiresIn
- Upload operation generates upload-capable URL
Temp Storage Cleanup
- A temp file older than
STORAGE_TEMP_CLEANUP_AFTERis removed by the next temp write, while that write's own file survives - The sweep never removes files outside
tmp/automations/, regardless of age STORAGE_TEMP_CLEANUP_AFTER=0disables sweeping entirely
Transform Image Action
- Resize creates resized image at destination
- A crop configuration is refused at validation
- Convert changes image format (png to webp)
- Quality setting controls output compression
- Without destination uses temp storage
- A two-dimension resize must name its
fit - An
avifoutput format is refused at validation
Upload Action
- Upload from base64 data with auto-detected contentType
- Upload from URL source to storage path
- Upload from previous step result key
- Upload to temp storage when no path specified
Last updated September 23, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.