unit U1 — 1 of 5
Files are bytes
base64, charsets, binary vs text
A file is bytes. Every other view of it — text, JSON, an image — is an interpretation, and picking the wrong one is where file automations go wrong. A PDF read as UTF-8 text produces mojibake; a CSV read as bytes cannot be parsed; an image passed through a JSON step is silently mangled. The first decision in any file flow is which of the two you are moving: raw bytes, or decoded text.
Between systems, bytes usually travel as base64 — a text-safe encoding that survives JSON and form fields at a cost of roughly 33% size. Decode it once, at the boundary, and keep the binary as a buffer from there. Text files need a second decision the byte view does not: the character encoding. UTF-8 is the right default and not a universal truth — exports from older Windows tooling arrive as Windows-1252 or UTF-16, and decoding those as UTF-8 turns every accented name into a question mark. The `content-type` header often carries the answer; trust it more than the file extension, which is a claim the uploader made.
Where it breaks: treating a binary file as a string. Passing a PDF through a step that concatenates or trims it corrupts the bytes irreversibly, and the failure surfaces much later as "the attachment will not open" rather than as an error in the run that caused it. Move binaries as buffers or as references, never through a text field.
worked example
Deciding once, at the boundary, whether this is a binary to move or text to read.
export const code = async (inputs) => {
const { contentType, base64 } = inputs.attachment;
// Decode base64 ONCE, into bytes. Everything downstream takes the buffer.
const bytes = Buffer.from(base64, 'base64');
const isText = /^(text\/|application\/(json|xml|csv))/.test(contentType);
if (!isText) {
// Binary: pass the BUFFER on, or store it and pass a reference. Never a string.
return { kind: 'binary', bytes, size: bytes.length, contentType };
}
// Text: the charset lives in the header, not the file extension — which is
// whatever the uploader chose to call it.
const charset = /charset=([\w-]+)/i.exec(contentType)?.[1] ?? 'utf-8';
return { kind: 'text', text: new TextDecoder(charset).decode(bytes), charset };
};field checklist
- Decide at the boundary: bytes to move, or text to read.
- Decode base64 once and keep a buffer, not a string.
- Read the charset from content-type, not from the file extension.
- Never pass a binary through a step that treats it as text.
- Budget for base64 being about a third larger than the raw bytes.
common failure — A PDF that would no longer open
An attachment was carried through the flow as a string so it could be logged for debugging. A trim step removed what it read as trailing whitespace — actually part of the file — and every archived invoice for three weeks was subtly corrupt. Nothing errored; the failure surfaced when a client tried to open one. Move binaries as buffers or as stored references, and if you must log them, log the size and a hash, never the content.
check your understanding
A CSV export from an older Windows tool shows every accented name as a question mark after your flow reads it. What is the most likely cause?
next unit opens once this is passed
sandbox validation
The check above confirms you followed the unit. Marking the module COMPLETED takes more: build the automation in your own engine and submit the exported flow and its run evidence, signed, to your unique validation URL. See the module page for that spec.