The Converter node converts data and files between formats without writing code. It is technology-neutral so more formats can be added over time. It converts CSV, JSON, and XML data in both directions, runs Tesseract OCR to pull text out of images and PDFs, transcribes speech in audio files to text, and rewrites a stored file in another format.
Overview
| Property | Value |
|---|---|
| Inputs | 1 |
| Outputs | 1 |
| Output | $nodeLabel.result (parsed data for csvToJson / xmlToJson, text for jsonToCsv / jsonToXml, extracted text for imageToText / pdfToText, the transcript for audioToText, new file metadata for fileConvert) |
Parameters
| Parameter | Type | Description |
|---|---|---|
label | string | Node identifier (camelCase) |
conversion | string | csvToJson (CSV text → array of row objects), jsonToCsv (array of objects/rows → CSV text), xmlToJson (XML text → object), jsonToXml (object → XML text), imageToText (OCR an image), pdfToText (OCR every selected PDF page), audioToText (voice transcribe: speech in an audio file → text), or fileConvert (rewrite a stored file in another format) |
source | expression | The data to convert. Leave empty to use the node's first input |
delimiter | string | Single-character field separator (default ,) |
hasHeader | boolean | csvToJson only — treat the first row as the header (default true) |
trimValues | boolean | csvToJson only — strip whitespace around header names and cell values (default true) |
includeHeader | boolean | jsonToCsv only — write a header row (default true) |
converterColumns | string | jsonToCsv only — optional comma-separated column order |
converterFileId | expression | File conversions — the Heym Drive file to read, e.g. $Upload.file.id |
converterTargetFormat | string | fileConvert only — output format (pdf, docx, html, md, txt, csv, epub, jpg, png, bmp, webp) |
ocrLanguage | string | OCR only — auto (default), a Tesseract code such as tur, or custom |
ocrLanguageCustom | string | OCR only — language codes used when ocrLanguage is custom, e.g. eng+tur |
ocrEncoding | string | OCR only — charset the text is normalized to (default utf-8) |
ocrNormalizeUnicode | boolean | OCR only — apply NFC normalization (default true) |
ocrPsm | string | OCR only — Tesseract page segmentation mode (default 3) |
ocrDpi | number | pdfToText only — page rasterization DPI (default 300) |
ocrPageRange | expression | pdfToText only — 3 or 2-5. Empty means every page |
sttLanguage | string | audioToText only: auto (default), a language code such as tr, or custom |
sttLanguageCustom | string | audioToText only: the language code used when sttLanguage is custom, e.g. yue |
Behavior
csvToJsonparses the source CSV text. WithhasHeader: trueeach row becomes an object keyed by the header values; withhasHeader: falseeach row becomes an array of cell values. Quoted fields, embedded delimiters, and embedded newlines are handled per RFC 4180. A leading UTF-8 BOM (common in Excel exports) is stripped, and duplicate header names are made unique without overwriting a real column (a, a, a_2→a,a_3,a_2). Setdelimiterto\tto parse tab-separated values.trimValues(defaulttrue) strips surrounding whitespace from header names and cell values, including inside quoted fields (" padded "→padded). RFC 4180 treats quoted whitespace as data, so settrimValues: falseto preserve it exactly.jsonToCsvbuilds CSV text from an array of objects (or arrays). Column order is taken fromconverterColumnswhen provided, otherwise inferred from the first object's keys. Values containing the delimiter, quotes, or newlines are quoted automatically.xmlToJsonparses XML into an object. Attributes use@prefixes and text mixed with attributes uses#text; repeated sibling elements become arrays. XML entity expansion is disabled.jsonToXmlserializes an object to an XML document. The object must have exactly one top-level key, which becomes the XML root element. Use the same@attributeand#textconvention asxmlToJsonto produce attributes and text alongside child elements.imageToTextruns Tesseract over a single image (PNG, JPEG, TIFF, WebP, BMP).pdfToTextrasterizes each selected page atocrDpiand recognizes it. Every page goes through OCR, so a digital PDF with a perfectly good text layer is still re-read from pixels. That keeps scanned and digital documents behaving identically, at the cost of speed, so narrowocrPageRangeon long files.audioToTexttranscribes the speech in an audio file (mp3, wav, ogg, opus, and flac; WhatsApp voice notes are.opusfiles). Recognition runs on the Heym instance itself with a Whisper model, so the audio never leaves it and no credential is needed. One multilingual model covers about a hundred languages and detects which one is spoken. Recordings can be up to 30 minutes long.fileConvertrewrites the source file in another format and stores the result as a new Drive file. The original is never modified. Documents go through pandoc, images through Pillow, and JSON to CSV through the Python csv writer. An image cannot become a document and a document cannot become an image; to read text out of an image useimageToText.
Getting the file in
File conversions read from Heym Drive, not from a path or a URL, so the file has to be stored first. Three common chains:
- File upload trigger → Converter — point
converterFileIdat$Upload.file.id. - Drive (
downloadUrl) → Converter — fetch a remote file into Drive, then pass$Download.id. - Agent → Converter — a skill that writes a file exposes it as
$reportAgent._generated_files[0].id.
converterFileId also accepts a whole file object ($Upload.file) or a Heym download URL; the file id is pulled out of either. The file must be one you own or one a teammate shared with you.
Languages
auto (the default) runs Tesseract's orientation-and-script detection first, then picks the best installed model for the detected script. Script models such as Latin or Cyrillic read every language written in that script, which is why auto handles a Turkish invoice and an English one without being told which is which.
Naming the language is still more accurate when you know it. Use a single code (tur), or join several with + (eng+tur) for mixed documents — more languages means slower and slightly noisier recognition, so keep the list short. Codes only work when the matching language data is installed on the backend; the node lists what is available when you pick a missing one.
Every Heym image ships osd, English, Turkish, German, French, Spanish, Italian, Portuguese, Dutch, Russian, Arabic, Simplified Chinese, Japanese, Korean, and the Latin, Cyrillic, Arabic, HanS, Japanese, and Hangul script models.
Voice transcription
audioToText reads a recording from Heym Drive and returns what was said. Typical sources are a voice note that arrived through a File upload trigger, a call recording fetched with a Drive node, or a file a Slack node downloaded.
Leave sttLanguage on auto and the model works out the language from the first seconds of speech, then reports it as $label.language. Name the language when you know it: short clips and recordings that open with silence or music are where detection is most likely to guess wrong. Codes are the two-letter ones Whisper uses, such as en, tr, de, es, fr, ar, zh, and ja.
The transcript comes back as one string in $label.result and as timed parts in $label.segments, which is what you want for subtitles or for quoting the moment something was said. Accuracy depends on the recording: clear speech close to the microphone transcribes well, while crosstalk, background noise, and names or product terms are where mistakes show up. Pass the transcript to an LLM or Agent node when you want a summary or a cleaned-up version.
Transcription is CPU work. Expect a recording to take a fraction of its own length on a few cores, and longer on a small instance. A file over 30 minutes is rejected, so split long recordings first.
Encoding
Recognized text is UTF-8 and keeps every character Tesseract produced. ocrNormalizeUnicode (on by default) applies NFC normalization, so a letter written as s plus a combining cedilla becomes a single ş — worth leaving on, because the two forms are not equal in comparisons or database lookups.
ocrEncoding matters only when something downstream cannot store the full Unicode range. Picking cp1254 or iso-8859-9 keeps Turkish text intact while guaranteeing the result fits that charset; latin-1 and ascii are narrower and replace what they cannot represent with ?. Leave it at utf-8 unless a target system forces your hand.
Output
csvToJson, jsonToCsv, xmlToJson, and jsonToXml return $label.result and $label.conversion. OCR conversions add:
| Field | Description |
|---|---|
$label.result | The full extracted text, pages joined by a blank line |
$label.language | The model that actually ran (useful to see what auto chose) |
$label.encoding | The charset the text was normalized to |
$label.page_count | Number of pages recognized |
$label.pages | Array of { page, text } for per-page handling |
$label.file | { id, filename, mime_type, size_bytes } of the source file |
audioToText adds:
| Field | Description |
|---|---|
$label.result | The full transcript |
$label.language | The language code that was detected, or the one you chose |
$label.duration_seconds | Length of the recording |
$label.segments | Array of { start, end, text } with times in seconds |
$label.file | { id, filename, mime_type, size_bytes } of the source file |
fileConvert returns the new file instead:
| Field | Description |
|---|---|
$label.id | UUID of the newly stored file |
$label.filename | Converted filename, base name kept and extension swapped |
$label.mime_type | MIME type of the new file |
$label.size_bytes | Size of the new file |
$label.download_url | Drive download URL for the new file |
$label.source_file | Metadata of the file that was converted |
Example
{
"type": "converter",
"data": {
"label": "toRows",
"conversion": "csvToJson",
"source": "$userInput.body.text",
"delimiter": ",",
"hasHeader": true
}
}For input name,age\nAda,36, downstream nodes access the parsed rows via $toRows.result ([{ "name": "Ada", "age": "36" }]).
Converting XML to JSON:
{
"type": "converter",
"data": {
"label": "xmlToObject",
"conversion": "xmlToJson",
"source": "$webhook.body"
}
}For <person id="1"><name>Ada</name></person>, $xmlToObject.result is { "person": { "@id": "1", "name": "Ada" } }. To create XML, pass that object to a second Converter node with conversion: "jsonToXml".
Reading a scanned invoice uploaded through a file upload trigger:
{
"type": "converter",
"data": {
"label": "readInvoice",
"conversion": "pdfToText",
"converterFileId": "$Upload.file.id",
"ocrLanguage": "tur",
"ocrEncoding": "utf-8",
"ocrDpi": 300,
"ocrPageRange": "1-3"
}
}An Agent or LLM node downstream can then pull structured fields out of $readInvoice.result.
Transcribing a voice note uploaded through a file upload trigger:
{
"type": "converter",
"data": {
"label": "transcribe",
"conversion": "audioToText",
"converterFileId": "$audio.file.id",
"sttLanguage": "auto"
}
}$transcribe.result holds the transcript and $transcribe.language the detected language, ready for an LLM node that summarizes the note or routes it.
Turning an agent's markdown report into a PDF:
{
"type": "converter",
"data": {
"label": "convertDoc",
"conversion": "fileConvert",
"converterFileId": "$reportAgent._generated_files[0].id",
"converterTargetFormat": "pdf"
}
}Downstream nodes read $convertDoc.download_url to share the PDF, or $convertDoc.id to keep working with it in Drive.
Requirements
OCR shells out to tesseract and poppler's pdftoppm, document conversion uses pandoc, and voice transcription runs a Whisper model. All of them ship with every way of running Heym, including run.sh, docker-compose, and the single release image on Docker or Kubernetes, so there is nothing to configure. Page limits, timeouts, the maximum DPI, and the maximum recording length are fixed platform values rather than settings.
Related
- Set – Transform and map individual fields
- JSON output mapper – Build a JSON response object
- File upload trigger – Accept a file into Heym Drive
- Drive – Store, fetch, and share files
- Node Types – Overview of all node types
- Expression DSL – Functions and syntax