Skip to main content
Updated Sep 7, 2026

Read File as Text

Use this action when a workflow or an agent needs the text inside a stored file — a contract a contact emailed in, a spreadsheet someone uploaded, a log an earlier step downloaded — so a later step can summarise it, search it, or decide what to do based on what it says.

Best for

  • Answering a contact's questions about the PDF or Word document they just attached
  • Pulling the text out of a file so an LLM step can summarise, classify or extract from it
  • Reading a plain-text file — a .txt, a CSV — that an earlier step downloaded to storage. A JSON export or a log reads back only when it was stored as text: see The kind comes from the stored content type
  • Finding out what an attachment actually is before spending anything on it: the media kind comes back even when the file cannot be read as text

Why it matters

Nothing else hands you the contents of a file. Inspect media tells you a file is 4 MB and two minutes long; Create storage signed URL gives you a link to it. Neither lets a step act on what the file says.

This action is the only one that returns text a later step can read, and it returns it in the same run — unlike Transcribe audio, which starts a background job, the text is in this step's result and the very next step can use it.

Main fields

FieldWhat it does
FileThe file to read, as an expression resolving to a file reference. In an agent run the incoming attachment is contactMessage.body.file; after an upload or download step it is step(0).file. A plain path string works too
Bucket and Full pathAn alternative to File when you want to point at the file directly. Bucket defaults to your company's storage, so Full path on its own is usually enough
Max CharactersOptional cap on how much text comes back. Capped at 2,000,000 either way. Leave it empty for the maximum
PurposeOptional label for what this step is for, shown in the logs. It does not change which file is read

Letting the agent pick the file

An agent tool parameter is a top-level value in expressions: a parameter named caminho is written caminho, not parameters.caminho — which resolves to nothing and quietly reads the wrong thing. So File can be {"fullPath": caminho} and the model chooses which file to read.

That matters when one incoming message carried several attachments. contactMessage.body.file names at most one of them, while the [attachment] … · ref: <path> lines the agent sees list every one — the model hands back the ref: path of the one it wants.

The file still has to belong to your company. A path outside it fails in exactly the same way as a file that does not exist — deliberately the same error, so this step cannot be used to find out whether another company's file exists.

What later steps can use

These values sit at the step root, so read them as step(N).text, not step(N).data.text. See Actions that publish at the step root.

ValueWhat it is
step(N).textThe extracted text. Empty when supported is false
step(N).supportedWhether the file could be read as text at all — see below
step(N).truncatedtrue when the text was cut short at a cap. Do not treat it as the whole document
step(N).reasonWhy the file could not be read, and which action handles it instead. Empty when supported is true
step(N).mediaKindtext, document, pdf, image, audio or video
step(N).mimeTypeThe stored content type
step(N).fileNameThe original file name the sender used, not the storage path
step(N).sizeSize in bytes

Which files read as text

KindFilesWhat you get
textAnything whose stored content type starts with text/text/plain, text/csv, text/markdownRead straight back
documentWord, Excel, PowerPoint and their OpenDocument equivalents — .doc, .docx, .odt, .rtf, .xls, .xlsx, .ods, .ppt, .pptx, .odpConverted to text on the server
pdf.pdfConverted to text on the server. A scanned PDF with no text layer comes back supported with empty text — there is no OCR
imagePhotos and picturesNot read. Vision-capable models are given images directly; use Inspect media for the dimensions
audioRecordings and voice notesNot read. Use Transcribe audio for a transcript
videoClips and screen recordingsNot read. Use Inspect media for the container facts, or Transform media to pull the audio out and then Transcribe audio

Anything the converter cannot open — a .zip, a .bin, a corrupt document — also comes back unread, with "This file type cannot be converted to text."

The kind comes from the stored content type, not the file name

The kind above is decided by the object's stored content type alone. text/… is text, application/pdf is pdf, image/…, audio/… and video/… are what they say, and everything else is document — which is then read only if it is one of the Office or PDF types in the table.

The file name gets exactly one second chance, and only inside the document lane: an Office or PDF extension rescues a document stored as application/octet-stream. .json, .log, .md and .yaml are on neither list, so for those the stored content type is the whole story:

Stored asWhat you get
text/plain, text/csv, text/markdownRead straight back
application/json — what a .json gets from an API's own Content-Type header, and from the auto-detection in Download to storage and Upload file to storagedocument, and not an extractable one: supported is false with "This file type cannot be converted to text."
application/octet-stream — where a .log, a .md or a .yaml lands when nothing sets a typeThe same

Renaming the file does not change any of this. Set the type when you store it: both Download to storage and Upload file to storage take an optional Content type, and text/plain there is what makes the file readable later.

A file that cannot be read is not a failed step

When the file is real but not text — an image, a recording, a video — the step succeeds. step(N).supported is false, step(N).reason explains what to use instead, and step_ok(N) stays true so the rest of the workflow still runs. Branch on the answer instead of relying on an error:

step(0).supported                    // did we actually get text?
step(0).mediaKind == "audio" // send it to Transcribe audio instead
step(0).truncated // only part of the document came back

Genuine failures do stop the step: no file at that path, a file belonging to another company, a document over the size limit, or a conversion the service accepted and could not finish.

One case looks like a failure and is not. Where the document converter is not configured at all — a self-hosted or local stack running without the transcoding service — every Office document and PDF comes back exactly like a .zip: supported false, the same "This file type cannot be converted to text." reason, and step_ok(N) still true. If a plain .docx reads on one environment and reads as an unsupported type on another, suspect the converter before the file.

Limits

  • Office documents and PDFs over 20 MiB are refused. That is a real step failure, not an unread file.
  • Text files are read up to 2 MiB and then cut, with truncated set. They are never refused for their size.
  • Text comes back capped at 2,000,000 characters, or at Max Characters when you set something lower. Check step(N).truncated before treating the text as the whole document.

What it costs

Reading a plain-text file just reads it, and costs nothing beyond the run itself.

Word, Excel, PowerPoint and PDF files are converted by the same service that handles media conversion, so those reads are billed against your transcoding allowance for as long as the conversion takes. There is no cache here, unlike Inspect media — reading the same document in two steps converts it twice. Read it once and keep the text in session state if more than one later step needs it.

Security

The text this step returns is untrusted content written by whoever made the file. Hidden spreadsheet cells, white-on-white paragraphs and speaker notes all survive extraction, and none of them were visible to the person who forwarded the document.

Treat it as material to summarise or quote, never as instructions to follow. When it feeds an LLM step, keep it in the message you send the model, not in the agent's system message.

Tips

  • Chain it directly: the incoming attachment in one step, this action in the next, and the LLM step that uses the text in the one after.
  • Check step(N).supported before handing step(N).text to a model. On an unread file the text is empty, and an empty document is the kind of thing a model will happily invent an answer about.
  • Set Max Characters when you only need the beginning — the first page of a contract, the head of a log — rather than paying to put a whole document into a model's context.
  • Use step(N).fileName when writing back to the contact. The storage path is an opaque id and means nothing to them.