Your Google Sheets node fails before it writes anything, and the executions list shows this:
columns.schema is required when columns.mappingMode is defineBelow
You hit this if you did any of three things: set documentId through the n8n REST API, imported a workflow JSON you downloaded or bought, or template-ized a working workflow to hand to someone else. What makes it confusing is that the editor looks completely normal. You open the node, and every mapping field is filled in — Vendor, Invoice #, Total, all of them, with expressions intact. Nothing is visibly missing. The node still refuses to run.
Root cause: columns has a second half you never see
The columns parameter on the Google Sheets node carries two things, not one.
value is the part you interact with. It is the visible mapping — target column on the left, expression on the right — and it is what gets saved into the workflow JSON when you fill in the form.
schema is an array that describes each target column: id, displayName, required, defaultMatch, display, type, and canBeUsedToMatch (whether that column is eligible as a matching key for update operations). You never type any of it. The UI builds it silently, at the moment you pick a spreadsheet from the Document dropdown and a tab from the Sheet dropdown — that selection triggers a fetch of the sheet's header row, and the header row becomes the schema.
That fetch is the whole story. It only happens through the editor's resource-locator dropdowns. Pasting raw JSON into n8n does not trigger it. Writing the workflow through the API does not trigger it. So you end up with value present and schema absent, and defineBelow mode treats that as an incomplete configuration.
What arrives in an imported template:
"columns": {
"mappingMode": "defineBelow",
"value": {
"Vendor": "={{ $json.vendor }}",
"Invoice #": "={{ $json.invoice_number }}",
"Total": "={{ $json.total }}"
}
}
What a node configured through the UI actually holds:
"columns": {
"mappingMode": "defineBelow",
"value": {
"Vendor": "={{ $json.vendor }}",
"Invoice #": "={{ $json.invoice_number }}",
"Total": "={{ $json.total }}"
},
"schema": [
{
"id": "Vendor",
"displayName": "Vendor",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
]
}
Same visible mapping. One of them runs.
Fix 1 — re-select the document and sheet (recommended, about ten seconds)
Open the failing node in the editor. On both the Document and Sheet fields, make sure the mode is "From list", then pick your spreadsheet and tab from the dropdowns again — even if the correct values already appear there. That selection is what fires the header fetch, and the schema regenerates on the spot. Save, run.
One practical detail worth the extra five seconds: after re-selecting, scroll down and confirm the column mapping is still fully populated. It occasionally clears when the schema is rebuilt, and an empty mapping writes an empty row rather than throwing, which is a much worse failure than the one you just fixed.
This is the fix for almost everyone: no JSON editing, and it produces exactly the state the node expects.
Fix 2 — switch to autoMapInputData
If you would rather not depend on a UI step at all, change the mapping mode and drop the explicit map:
"columns": {
"mappingMode": "autoMapInputData",
"value": {}
}
In this mode the node matches incoming JSON keys against the sheet's headers itself, so no schema is required.
The tradeoff is real. Your field names now have to match the sheet headers exactly — same spelling, same case, same spacing. A header like Invoice # or Due Date means your upstream Code node has to emit a key literally named Invoice #, spaces and symbol included. That is brittle, and it breaks silently the day someone renames a column in the spreadsheet.
autoMapInputData is genuinely the better choice when you control both sides: you own the sheet, you own the node that shapes the data, and you can keep the header row and the JSON keys named identically on purpose. It is the wrong choice when the sheet belongs to someone else.
Fix 3 — build the schema yourself
If you create workflows through the n8n API, generate templates programmatically, or patch workflow JSON in a build step, you need the schema without ever opening a browser. It is just a mapping over your header row:
const headers = ['Vendor', 'Invoice #', 'Date', 'Total'];
const schema = headers.map((name) => ({
id: name,
displayName: name,
required: false,
defaultMatch: false,
display: true,
type: 'string',
canBeUsedToMatch: true
}));
// inject into the target node before pushing the workflow
const node = workflow.nodes.find((n) => n.name === 'Append Invoice Row');
node.parameters.columns.schema = schema;
The id and displayName must match the sheet's header text exactly — that is the join key between your mapping and the actual columns. Keep the header array in one place and derive both the schema and the value map from it, so a renamed column cannot desynchronize the two halves.
Why this bites template buyers in particular
Every single person who imports a workflow JSON containing a defineBelow Sheets node walks into this, because importing is precisely the path that skips the header fetch. Their setup was not wrong. Nothing in the file is broken.
If you sell or share n8n workflows, that makes it a documentation duty rather than a product defect. Put an explicit line in your setup instructions: after import, open the Google Sheets node and re-select your spreadsheet and sheet from the "From list" dropdowns, then confirm the column mapping is still fully populated. One sentence removes an entire class of support message, and its absence turns a working template into a refund request.
A related trap while you are in there
The HTTP Request node does not pass binary data through. Its output is the API response and nothing else, so a PDF that arrived on your trigger is gone by the time a downstream Drive upload asks for it. Re-attach it from the node that still has it, in a Code node after the HTTP call:
return [{ json: $json, binary: $('Pick PDF Attachment').item.binary }];
Both of these, plus validation, retries, and non-blocking notifications, are covered in six reliability patterns for n8n + AI workflows.
If you want finished workflows with the setup docs already written, they are at https://hamedlight63.gumroad.com — the fixes above stand on their own either way.
Top comments (0)