Custom Node - File upload operation in declarative-style node

Describe the problem/error/question

I am creating a custom node where one of the operations is a file upload. I want to let the users insert a binary file just like many other nodes. One of the problem is that all those nodes are using the programmatic style.

We chose to build the node using the declarative style. Now, we can’t seem to find a way to make it work using this style. Is it even possible or do we have to swap the style to the programmatic style?

We send the file as a multipart/form-data with the key documents.

What is the error message (if any)?

Can’t find the way of structuring the operation without getting Invalid Syntax.

Code:

Node Properties:

{
		displayName: 'Binary Property',
		name: 'binaryPropertyName',
		type: 'string',
		displayOptions: {
			show: {
				operation: ['uploadDocument'],
			},
		},
		default: 'data',
		required: true,
		description:
			'Name of the binary property containing the file to upload. Usually `data` when coming from another node (e.g., HTTP Request).',
		placeholder: 'data',
	},

Try number 1:

export const uploadDocument: INodePropertyOptions = {
	name: 'Upload a Document',
	value: 'uploadDocument',
	description: 'Upload a document to associate with an entity',
	action: 'Upload a Document',
	routing: {
		request: {
			method: 'POST',
			url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
			headers: {
				'Content-Type': 'multipart/form-data',
			},
			body: {
				mode: 'formData',
				formData: [
					{
						documents: {
							uri: '={{ $parameter.binaryPropertyName.binary.data}}',
							name: '={{ $parameter.binaryPropertyName.binary.fileName }}',
							type: '={{ $parameter.binaryPropertyName.binary.mimeType }}',
						},
					},
				],
			},
		},
		output: {
			postReceive: [
				{
					type: 'set',
					properties: {
						value: '={{ $response.body }}',
					},
				},
			],
		},
	},
};

Try number 2:

export const uploadDocument: INodePropertyOptions = {
	name: 'Upload a Document',
	value: 'uploadDocument',
	description: 'Upload a document to associate with an entity',
	action: 'Upload a Document',
	routing: {
		request: {
			method: 'POST',
			url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
			headers: {
				'Content-Type': 'multipart/form-data',
			},
			body: {
				mode: 'formData',
				formData: [
					{
						parameterType: 'formBinaryData',
						name: 'documents',
						inputDataFieldName: '={{ $parameter.binaryPropertyName }}',
					},
				],
			},
		},
		output: {
			postReceive: [
				{
					type: 'set',
					properties: {
						value: '={{ $response.body }}',
					},
				},
			],
		},
	},
};

Thank you!

Welcome to the N8n community @ChenBr

Your try number 2 is very close to the correct solution. The error is likely because you are mixing concepts. In the declarative style, you don’t use inputDataFieldName for this purpose.

Try this instead


routing: {
    request: {
        method: 'POST',
        url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
        // Headers are often handled automatically for multipart/form-data
        // You can usually omit the Content-Type header; n8n will set it correctly.
        body: {
            mode: 'form-data', // Use a hyphen, not camelCase
            formData: {
                // This is the key: the property name is the form key ('documents')
                documents: [
                    {
                        // This is a special object that tells n8n to get the binary data
                        parameterType: 'formBinaryData',
                        // This must match the name of your binary property parameter
                        binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
                    },
                ],
            },
        },
    },
    output: {
        postReceive: [
            {
                type: 'set',
                properties: {
                    value: '={{ $response.body }}',
                },
            },
        ],
    },
},

Hello @Zelite , thank you for the help.
Unfortunately I still receive an Invalid Syntax error:

Here is the full code, just in case:

import type { INodeProperties, INodePropertyOptions } from 'n8n-workflow';

export const uploadDocument: INodePropertyOptions = {
	name: 'Upload a Document',
	value: 'uploadDocument',
	description: 'Upload a document to associate with an entity',
	action: 'Upload a Document',
	routing: {
		request: {
			method: 'POST',
			url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
			body: {
				formData: {
					documents: [
						{
							parameterType: 'formBinaryData',
							binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
						},
					],
				},
			},
		},
		output: {
			postReceive: [
				{
					type: 'set',
					properties: {
						value: '={{ $response.body }}',
					},
				},
			],
		},
	},
};

export const uploadDocumentFields: INodeProperties[] = [
	{
		displayName: 'מזהה',
		name: 'id',
		type: 'string',
		displayOptions: {
			show: {
				operation: ['uploadDocument'],
			},
		},
		default: '',
		required: true,
		description: 'Entity ID (UUID format)',
		placeholder: 'e.g., 123e4567-e89b-12d3-a456-426614174000',
	},
	{
		displayName: 'סוג מזהה',
		name: 'idType',
		type: 'options',
		displayOptions: {
			show: {
				operation: ['uploadDocument'],
			},
		},
		options: [
			{ name: 'לקוח', value: 'cid' },
			{ name: 'ליד', value: 'lid' },
			{ name: 'תהליך', value: 'wid' },
			{ name: 'מעסיק', value: 'employerId' },
			{ name: 'פגישה', value: 'meetingId' },
		],
		default: 'cid',
		required: true,
		description: 'Type of entity to associate the document with',
	},
	{
		displayName: 'Binary Property',
		name: 'binaryPropertyName',
		type: 'string',
		displayOptions: {
			show: {
				operation: ['uploadDocument'],
			},
		},
		default: 'data',
		required: true,
		description:
			'Name of the binary property containing the file to upload. Usually `data` when coming from another node (e.g., HTTP Request).',
		placeholder: 'data',
	},
];

Thank you once again,

Chen

Your code is missing mode: 'form-data', which caused the “Invalid Syntax” error


routing: {
	request: {
		method: 'POST',
		url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
		body: {
			// 1. Add the missing 'mode' property
			mode: 'form-data',
			// 2. The structure under 'formData' is correct
			formData: {
				documents: [
					{
						parameterType: 'formBinaryData',
						binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
					},
				],
			},
		},
	},
	output: {
		postReceive: [
			{
				type: 'set',
				properties: {
					value: '={{ $response.body }}',
				},
			},
		],
	},
},

Thank you again for the reply @Zelite .
You are right, I forgot the mode.

But still, I get the same error, even after adding the mode. (Invalid Syntax)
Is there any source you are using? Do you have any example for it? I don’t want to bother.

Thanks!

export const uploadDocument: INodePropertyOptions = {
	name: 'Upload a Document',
	value: 'uploadDocument',
	description: 'Upload a document to associate with an entity',
	action: 'Upload a Document',
	routing: {
		request: {
			method: 'POST',
			url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
			body: {
				mode: 'form-data',
				formData: {
					documents: [
						{
							parameterType: 'formBinaryData',
							binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
						},
					],
				},
			},
		},
		output: {
			postReceive: [
				{
					type: 'set',
					properties: {
						value: '={{ $response.body }}',
					},
				},
			],
		},
	},
};

You are not bothering at all and btw I’ll soon be an official n8n support team member so get familiar :rofl:

The issue is likely that the structure inside formData is not exactly what n8n expects

Please try this


routing: {
    request: {
        method: 'POST',
        url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
        body: {
            mode: 'form-data',
            formData: {
                // The key "documents" must match what your API expects
                documents: [
                    {
                        // Use 'binaryData' instead of 'formBinaryData'
                        parameterType: 'binaryData',
                        // The key must be 'binaryPropertyName'
                        binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
                    },
                ],
            },
        },
    },
    output: {
        postReceive: [
            {
                type: 'set',
                properties: {
                    value: '={{ $response.body }}',
                },
            },
        ],
    },
},

Here’s another alternative to try


formData: {
    documents: [
        {
            parameterType: 'binaryData',
            name: 'file', // Try with and without this line
            binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
        },
    ],
},

And if this doesn’t still work, I’ll let one of the top N8n supporter know of this, so you can get the help you need.

I see, good luck :slight_smile:

I tried all three suggestions you gave, I received the same error in all but the alternative try without the name: file:

export const uploadDocument: INodePropertyOptions = {
	name: 'Upload a Document',
	value: 'uploadDocument',
	description: 'Upload a document to associate with an entity',
	action: 'Upload a Document',
	routing: {
		request: {
			method: 'POST',
			url: '=/documents/upload?{{ $parameter.idType }}={{ $parameter.id }}',
			body: {
				mode: 'form-data',
				formData: {
					documents: [
						{
							parameterType: 'binaryData',
							binaryPropertyName: '={{ $parameter.binaryPropertyName }}',
						},
					],
				},
			},
		},
		output: {
			postReceive: [
				{
					type: 'set',
					properties: {
						value: '={{ $response.body }}',
					},
				},
			],
		},
	},
};

The thing is, it doesn’t look like that data is being sent correctly, the content type is application/json even though we set the mode as form-data.
I also tried and added json: false, which didn’t help.

Thanks!

@BramKn please look at this!

@ChenBr someone more knowledgeable in this will look at it.

1 Like

Thank you!

By the way, where did you find the properties you have told me to add? the parameterType and `binaryPropertyName`? I couldn’t find anything regarding it in the docs and the interfaces: