Describe the problem/error/question
I need to execute an HTTP request where the server expects me to provide multiple files under the same filed data. The number of files is not static so I cannot just map every file to a field in the n8n node. Is that even possible via the http node or should I use a code node?
What is the error message (if any)?
I tried it like this but obviously if I have more than 1 file I get an error that the binary with id , doesn’t exist.
Please share your workflow
Information on your n8n setup
- n8n version: 2.21.7
- Database (default: SQLite): postgres
- n8n EXECUTIONS_PROCESS setting (default: own, main): own
- Running n8n via (Docker, npm, n8n cloud, desktop app): docker
- Operating system: macOS
Hello @kjooleng thanks for the suggestion!
I ran into 2 issues: first your Construct Multipart Body works well for text but files like pdf and docx get absolutely muffled by the approach.
I modified it to:
the second is that when I feed the request to the service now I get a:
Content-Type is not supported
I haven’t gotten to debugging that yet.
Here is the modified json
This allows you to verify that the logic works for both metadata (JSON) and binary content (simulating your PDFs/Docs) without needing your actual files present.
Thanks for the edited mock data generator.
I looked into why the service was returning
Content-Type is not supported and it looks like the HTTP node doesn’t send anything.
I tried setting it to n8n-binary + filed name multipartBody but I got an error " source.on is not a function" - I probably have messed up something with the javascript as I am not very experienced with n8n’s functions. I’ll dig around the documentation and try and figure it out but if I’m missing something obvious any input is welcome.
Use this specific Code Node and these exact HTTP Request settings
return (async () => {
const CRLF = '\r\n';
const items = $input.all();
const result = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
// 1. Create a unique boundary
const boundary = `----n8nboundary${Math.random().toString(36).slice(2, 11)}`;
const chunks = [];
const pushText = (text) => chunks.push(Buffer.from(text, 'utf8'));
// 2. ADD THE 'aggregate' FIELD MANUALLY
// Since we are in 'Binary' mode, this MUST be in the buffer
pushText(`--${boundary}${CRLF}`);
pushText(`Content-Disposition: form-data; name="aggregate"${CRLF}`);
pushText(`${CRLF}true${CRLF}`);
// 3. Handle 'params' file
if (item.binary?.params) {
const paramsMeta = item.binary.params;
const paramsBuf = await this.helpers.getBinaryDataBuffer(i, 'params');
const fileName = paramsMeta.fileName || 'params.json';
const mimeType = paramsMeta.mimeType || 'application/json';
pushText(`--${boundary}${CRLF}`);
pushText(`Content-Disposition: form-data; name="params"; filename="${fileName}"${CRLF}`);
pushText(`Content-Type: ${mimeType}${CRLF}${CRLF}`);
chunks.push(paramsBuf);
pushText(CRLF);
}
// 4. Handle all other files (mapped to 'data')
if (item.binary) {
const dataKeys = Object.keys(item.binary).filter((k) => k !== 'params');
for (const key of dataKeys) {
const fileMeta = item.binary[key];
const fileBuf = await this.helpers.getBinaryDataBuffer(i, key);
const fileName = fileMeta.fileName || key;
const mimeType = fileMeta.mimeType || 'application/octet-stream';
pushText(`--${boundary}${CRLF}`);
pushText(`Content-Disposition: form-data; name="data"; filename="${fileName}"${CRLF}`);
pushText(`Content-Type: ${mimeType}${CRLF}${CRLF}`);
chunks.push(fileBuf);
pushText(CRLF);
}
}
// 5. Close boundary
pushText(`--${boundary}--${CRLF}`);
const body = Buffer.concat(chunks);
// The exact header the server needs to see
const contentTypeHeader = `multipart/form-data; boundary=${boundary}`;
result.push({
json: item.json,
binary: {
multipartBody: await this.helpers.prepareBinaryData(
body,
'request.bin',
contentTypeHeader
),
},
});
}
return result;
})();
Do not add anything to “Body Parameters”. Configure http request like this:
- Method:
POST
- URL:
your-api-endpoint
- Send Body:
true
- Body Content Type:
Binary ← This is the most important part
- Input Data Field Name:
multipartBody
- Options: (Leave empty)
What version of n8n are using. I tried couple and I was only able to get it going consistently on
1.123.65. Sadly when I was able to run it n8n actually tried to upload the body sending every single bit as its own form-data:
Host: javeRequestCatcher.127.0.0.1.nip.io
Accept: application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7
Accept-Encoding: gzip, compress, deflate, br
Connection: close
Content-Length: 69898
Content-Type: multipart/form-data; boundary=--------------------------6c56103870e43e66b91b529d
User-Agent: axios/1.18.0
----------------------------6c56103870e43e66b91b529d
Content-Disposition: form-data; name="0"
45
----------------------------6c56103870e43e66b91b529d
Content-Disposition: form-data; name="1"
45
<skip a lot of lines>
13
----------------------------6c56103870e43e66b91b529d
Content-Disposition: form-data; name="668"
10
----------------------------6c56103870e43e66b91b529d--
On I wanna say every 2.xx version I tried I just got source.on is not a function as an error
Will this help?
-
Delete the HTTP Request node.
-
Put this code in a Code Node.
const axios = require('axios');
const FormData = require('form-data');
const item = $input.first();
const form = new FormData();
// 1. Add 'aggregate'
form.append('aggregate', 'true');
// 2. Add 'params'
if (item.binary?.params) {
const buf = await this.helpers.getBinaryDataBuffer(0, 'params');
form.append('params', buf, { filename: 'params.json', contentType: 'application/json' });
}
// 3. Add all other files to 'data'
if (item.binary) {
const keys = Object.keys(item.binary).filter(k => k !== 'params');
for (const key of keys) {
const buf = await this.helpers.getBinaryDataBuffer(0, key);
const file = item.binary[key];
form.append('data', buf, { filename: file.fileName || key, contentType: file.mimeType });
}
}
// 4. Execute request directly
try {
const response = await axios.post('http://example.org/project/tag', form, {
headers: form.getHeaders(),
});
return { json: response.data };
} catch (error) {
return { json: { error: error.message } };
}
- Go to your
Settings (or Environment variables) and ensure that NODE_FUNCTION_ALLOW_EXTERNAL includes axios and form-data if your specific instance restricts them (though they are usually bundled). If it’s n8n Cloud or standard Docker, axios is built-in.
That worked great until the second the endpoint required authentication - custom code nodes do not have permissions to work with saved credentials.
After a bunch brainstorming I ended up creating a custom node. Currently the implementation is quick and dirty, but if ever have time I’ll get it up to standard and post it as a community node.