A request node’s body is a plain string most of the time: JSON, XML, form-encoded text, whatever
you typed. Two things cannot be expressed that way, so body also accepts a structured block
picked by its type field.
Uploading a file
- id: upload_avatar
type: request
config:
method: POST
url: ${{ variables.baseUrl }}/users/{id}/avatar
pathParams:
id: ${{ variables.userId }}
body:
type: multipart
fields:
- name: caption
value: Profile photo # a plain text field
- name: file
filePath: ./fixtures/avatar.png # resolved relative to the flow file
contentType: image/png # optional, defaults to application/octet-stream
filename: avatar.png # optional, defaults to the basename of filePath
next: verify_upload
Each entry in fields is either a text field (value) or a file read from disk (filePath).
Exactly one of the two: setting both, or neither, is a parse error rather than a surprise at run
time.
Bandura writes the Content-Type header itself, including the generated boundary, and it
overrides any Content-Type your node’s headers set. A hand-written boundary could never match
the one the body was actually built with, so there is nothing useful to preserve.
Uploading something you just generated
A text field may also carry a filename. That sends its inline value as a named file part rather
than an ordinary form field, which is how content a script node produced gets uploaded without
first writing it to disk:
Needs
@bandura/cli1.0.0-rc.4 or the desktop app 1.0.0-rc.5 or newer; every current build qualifies. The parser has always acceptedfilenameon a text field, but desktop 1.0.0-rc.4 and earlier drop it and ship the part as a plain form field, so the server sees a different request with nothing to say so.
- id: build_csv
type: script
config:
code: |
variables.report = "id,total\n1,9.99\n2,14.50\n";
next: upload_csv
- id: upload_csv
type: request
config:
method: POST
url: ${{ variables.baseUrl }}/imports
body:
type: multipart
fields:
- name: file
value: ${{ variables.report }}
filename: report.csv
contentType: text/csv
next: null
Sending raw bytes
- id: upload_report
type: request
config:
method: POST
url: ${{ variables.baseUrl }}/reports
body:
type: binary
filePath: ./fixtures/report.pdf
contentType: application/pdf
next: null
type: binary sends the bytes as the whole body, with no encoding around them. Use it for the APIs
that take a file directly on PUT or POST rather than wrapped in a form.
The source is either filePath or an inline base64 string, again exactly one of the two:
body:
type: binary
base64: ${{ $base64(variables.generatedPayload) }}
contentType: application/octet-stream
contentType is required here, because there is no sensible default for arbitrary bytes. It
becomes the Content-Type header only if your headers did not already set one, in any casing.
Where file paths resolve
filePath is resolved relative to the flow file that declares it, unless it is already
absolute. That is the same convention tls.cert and a gRPC node’s proto use, so a flow and its
fixtures move together as one folder.
A path that does not resolve to a readable file fails the node with an error naming the field, the
path it tried, and the directory it tried it from. You will not get a bare ENOENT with no
indication of which of five fields caused it.
Interpolation works throughout
Every string in either block goes through ${{ }}: a field’s name, value, filePath,
contentType and filename, and a binary body’s filePath, base64 and contentType. So the
fixture directory can come from an environment variable, and
dynamic values work here as they do anywhere else.
Editing one in the app
Everything above is editable from the inspector’s Body tab, which follows the shape of the body rather than assuming it is a string.
For a multipart body you get one card per field, each switching between a text value and a
file. Choose file… opens your operating system’s picker. filename and contentType sit
on the field that has them, which makes filename reachable outside the YAML for the first time.
For a binary body you pick between a filePath and an inline base64 string and set the
contentType the format requires.
Two things about the editors are worth knowing, because both are deliberate:
- The picker writes an absolute path. It has no way to know which flow is open, and an absolute path is always correct. Shortening it to a relative one is a manual edit, and the panel says what a relative path resolves against rather than guessing a base for you.
- An edit is held back until it validates. Adding a field, or flipping one from text to file, passes through a state the format does not accept. Rather than write that and have the flow go stale, the panel shows what you are typing while the file keeps the last version that parsed, and tells you it is doing so. The moment the block is valid again it is written.
The panel also names what the body is, listing a multipart body’s field names or a binary body’s
source and content type, and repeats for a multipart body that the engine owns the Content-Type
header.
On 1.0.0-rc.7 and earlier there are no structured editors, and the Body tab points you at the Node tab’s YAML instead. The summary above needs rc.5 or newer: on rc.4 and earlier the Body tab and the request preview render a node with a structured body as though it sends no body at all. What went on the wire was never affected, only the panels describing it.
Things worth knowing
- A structured body and
graphqlare mutually exclusive, the same rule a string body already had. A GraphQL request builds its own body fromgraphql.query. - The Result panel shows what actually shipped. For a structured body, the request snapshot in the inspector and in the run history is the built wire bytes, not the YAML block you wrote. That is the same “what got sent” contract a hand-written body gives.
- Large files are read into memory for the request. Bandura is a testing tool, so this is fine for fixtures and unhelpful for a multi-gigabyte upload.
What to read next
- Node reference for everything else on a request node.
- Dynamic values for generating file content or names inline.
- Client certificates & proxies if the upload endpoint is behind mTLS.