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
| Field | What it does |
|---|---|
| File | The 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 path | An 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 Characters | Optional cap on how much text comes back. Capped at 2,000,000 either way. Leave it empty for the maximum |
| Purpose | Optional 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.
| Value | What it is |
|---|---|
step(N).text | The extracted text. Empty when supported is false |
step(N).supported | Whether the file could be read as text at all — see below |
step(N).truncated | true when the text was cut short at a cap. Do not treat it as the whole document |
step(N).reason | Why the file could not be read, and which action handles it instead. Empty when supported is true |
step(N).mediaKind | text, document, pdf, image, audio or video |
step(N).mimeType | The stored content type |
step(N).fileName | The original file name the sender used, not the storage path |
step(N).size | Size in bytes |
Which files read as text
| Kind | Files | What you get |
|---|---|---|
text | Anything whose stored content type starts with text/ — text/plain, text/csv, text/markdown | Read straight back |
document | Word, Excel, PowerPoint and their OpenDocument equivalents — .doc, .docx, .odt, .rtf, .xls, .xlsx, .ods, .ppt, .pptx, .odp | Converted to text on the server |
pdf | .pdf | Converted to text on the server. A scanned PDF with no text layer comes back supported with empty text — there is no OCR |
image | Photos and pictures | Not read. Vision-capable models are given images directly; use Inspect media for the dimensions |
audio | Recordings and voice notes | Not read. Use Transcribe audio for a transcript |
video | Clips and screen recordings | Not 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 as | What you get |
|---|---|
text/plain, text/csv, text/markdown | Read 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 storage | document, 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 type | The 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
truncatedset. 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).truncatedbefore 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).supportedbefore handingstep(N).textto 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).fileNamewhen writing back to the contact. The storage path is an opaque id and means nothing to them.