Converter

The Converter node converts data and files between formats without writing code. It is technology-neutral so more formats can be added over time.

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

PropertyValue
Inputs1
Outputs1
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

ParameterTypeDescription
labelstringNode identifier (camelCase)
conversionstringcsvToJson (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)
sourceexpressionThe data to convert. Leave empty to use the node's first input
delimiterstringSingle-character field separator (default ,)
hasHeaderbooleancsvToJson only — treat the first row as the header (default true)
trimValuesbooleancsvToJson only — strip whitespace around header names and cell values (default true)
includeHeaderbooleanjsonToCsv only — write a header row (default true)
converterColumnsstringjsonToCsv only — optional comma-separated column order
converterFileIdexpressionFile conversions — the Heym Drive file to read, e.g. $Upload.file.id
converterTargetFormatstringfileConvert only — output format (pdf, docx, html, md, txt, csv, epub, jpg, png, bmp, webp)
ocrLanguagestringOCR only — auto (default), a Tesseract code such as tur, or custom
ocrLanguageCustomstringOCR only — language codes used when ocrLanguage is custom, e.g. eng+tur
ocrEncodingstringOCR only — charset the text is normalized to (default utf-8)
ocrNormalizeUnicodebooleanOCR only — apply NFC normalization (default true)
ocrPsmstringOCR only — Tesseract page segmentation mode (default 3)
ocrDpinumberpdfToText only — page rasterization DPI (default 300)
ocrPageRangeexpressionpdfToText only — 3 or 2-5. Empty means every page
sttLanguagestringaudioToText only: auto (default), a language code such as tr, or custom
sttLanguageCustomstringaudioToText only: the language code used when sttLanguage is custom, e.g. yue

Behavior

  • csvToJson parses the source CSV text. With hasHeader: true each row becomes an object keyed by the header values; with hasHeader: false each 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). Set delimiter to \t to parse tab-separated values.
  • trimValues (default true) strips surrounding whitespace from header names and cell values, including inside quoted fields (" padded " → padded). RFC 4180 treats quoted whitespace as data, so set trimValues: false to preserve it exactly.
  • jsonToCsv builds CSV text from an array of objects (or arrays). Column order is taken from converterColumns when provided, otherwise inferred from the first object's keys. Values containing the delimiter, quotes, or newlines are quoted automatically.
  • xmlToJson parses XML into an object. Attributes use @ prefixes and text mixed with attributes uses #text; repeated sibling elements become arrays. XML entity expansion is disabled.
  • jsonToXml serializes an object to an XML document. The object must have exactly one top-level key, which becomes the XML root element. Use the same @attribute and #text convention as xmlToJson to produce attributes and text alongside child elements.
  • imageToText runs Tesseract over a single image (PNG, JPEG, TIFF, WebP, BMP).
  • pdfToText rasterizes each selected page at ocrDpi and 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 narrow ocrPageRange on long files.
  • audioToText transcribes the speech in an audio file (mp3, wav, ogg, opus, and flac; WhatsApp voice notes are .opus files). 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.
  • fileConvert rewrites 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 use imageToText.

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 converterFileId at $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:

FieldDescription
$label.resultThe full extracted text, pages joined by a blank line
$label.languageThe model that actually ran (useful to see what auto chose)
$label.encodingThe charset the text was normalized to
$label.page_countNumber of pages recognized
$label.pagesArray of { page, text } for per-page handling
$label.file{ id, filename, mime_type, size_bytes } of the source file

audioToText adds:

FieldDescription
$label.resultThe full transcript
$label.languageThe language code that was detected, or the one you chose
$label.duration_secondsLength of the recording
$label.segmentsArray 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:

FieldDescription
$label.idUUID of the newly stored file
$label.filenameConverted filename, base name kept and extension swapped
$label.mime_typeMIME type of the new file
$label.size_bytesSize of the new file
$label.download_urlDrive download URL for the new file
$label.source_fileMetadata 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.