Hey Buddies , i have created a workflow in n8n , in that i have a google sheets node , that node i need to control with REST api so, 80% done , The issue i am facing is that i am unable to control document _mode through my restapi how can i do that
Share more screenshot or the workflow might be helpful.
Can’t guess the real issue here.
This below is the screenshot of configuration i need to get in my sheets through a REST api
Api Reference: router.post(“/workflows/:id/add-google-sheets”, async (req, res) => {
try {
const id = req.params.id;
const {
nodeName = “Append to Google Sheets”,
documentUrl, // preferred: full URL from UI
documentId: documentUrlOrId, // legacy: URL or bare ID
range = “Sheet1!A:Z”,
googleCredentialId,
// we’ll force modern (v6) for Option A
headerMapping = { Date: “Date”, Name: “Name”, Mobile: “Mobile” }, // header → payload key
mapping = { A: “Date”, B: “Name”, C: “Mobile” }, // v4 fallback (A/B/C)
} = req.body || {};
// — helpers —
const isUrl = (s) => /^https?:\/\//i.test(String(s || “”));
const extractSheetId = (str) => {
if (!str) return “”;
const m = String(str).match(/\/spreadsheets\/d\/([a-zA-Z0-9-_]+)/);
return m ? m[1] : String(str).trim();
};
const parseSheetNameFromRange = (rng) => {
const m = String(rng || “”).match(/^([^!]+)!/);
return m ? m[1] : “Sheet1”;
};
const rawDoc = documentUrl ?? documentUrlOrId;
if (!rawDoc) {
return res.status(400).json({ error: “documentUrl (or documentId) is required” });
}
const canonicalUrl = isUrl(rawDoc)
? String(rawDoc)
: `https://docs.google.com/spreadsheets/d/${extractSheetId(rawDoc)}/edit\`;
const sheetName = parseSheetNameFromRange(range);
// — load workflow & webhook —
const wf = await getWorkflow(id);
const webhookNode =
wf.nodes.find((n) => n.type === “n8n-nodes-base.webhook”) ||
wf.nodes.find((n) => n.name === “Webhook”);
if (!webhookNode) {
return res.status(400).json({ error: “No Webhook node found in this workflow” });
}
// — Option A: Modern UI, no list lookups (sheet by name) —
// Variant 1: nested objects (many builds accept this)
const paramsModernV1 = {
resource: “sheetWithinDocument”,
operation: “appendRow”,
document: { mode: “url”, value: canonicalUrl }, // Document → By URL
sheet: { mode: “name”, value: sheetName }, // Sheet → By name (e.g., “Sheet1”)
columnsMode: “mapEachColumnManually”,
columnsUi: {
column: Object.entries(headerMapping).map(([header, key]) => ({
column: header,
value: `={{$json[“${key}”]}}`,
})),
},
options: {},
};
// Variant 2: flattened keys (other builds prefer this)
const paramsModernV2 = {
resource: “sheetWithinDocument”,
operation: “appendRow”,
documentMode: “url”,
documentUrl: canonicalUrl,
sheetMode: “name”,
sheetName,
columnsMode: “mapEachColumnManually”,
columnsUi: {
column: Object.entries(headerMapping).map(([header, key]) => ({
column: header,
value: `={{$json[“${key}”]}}`,
})),
},
options: {},
};
// — v4 fallback (kept for resilience; targets Sheet1 via range) —
const paramsV4 = {
operation: “append”,
documentId: canonicalUrl, // URL works in many n8n builds; bare ID works as well
range, // e.g., “Sheet1!A:Z”
valueInputMode: “RAW”,
fieldsUi: {
field: Object.entries(mapping).map(([col, key]) => ({
column: col,
value: `={{$json[“${key}”]}}`,
})),
},
};
// — add/update node helper —
const isSheets = (n) => n.type === “n8n-nodes-base.googleSheets”;
const existingIdx = wf.nodes.findIndex(isSheets);
const willUpdate = existingIdx >= 0;
const baseNode = {
id: willUpdate ? wf.nodes[existingIdx].id : `${nodeName.replace(/\s+/g, “_”)}_${Date.now()}`,
name: willUpdate ? wf.nodes[existingIdx].name : nodeName,
type: “n8n-nodes-base.googleSheets”,
typeVersion: 6, // force modern for Option A
position: willUpdate
? wf.nodes[existingIdx].position
: [(webhookNode.position?.[0] ?? 260) + 300, webhookNode.position?.[1] ?? 300],
…(googleCredentialId
? { credentials: { googleSheetsOAuth2Api: { id: Number(googleCredentialId) } } }
: {}),
};
const putWithParams = async (parameters, forceTypeVersion) => {
const node = {
…baseNode,
…(forceTypeVersion ? { typeVersion: forceTypeVersion } : {}),
parameters,
};
let nodes = wf.nodes.slice();
if (willUpdate) nodes[existingIdx] = { …wf.nodes[existingIdx], …node };
else nodes = […nodes, node];
const connections = { …(wf.connections || {}) };
const fromKey = webhookNode.name;
if (!connections[fromKey]) connections[fromKey] = { main: [[]] };
if (!connections[fromKey].main) connections[fromKey].main = [[]];
const targetName = willUpdate ? nodes[existingIdx].name : node.name;
const already = connections[fromKey].main[0].some((e) => e.node === targetName);
if (!already) connections[fromKey].main[0].push({ node: targetName, type: “main”, index: 0 });
const updated = await updateWorkflow(id, {
name: wf.name,
nodes,
connections,
settings: wf.settings || {},
staticData: wf.staticData || {},
});
return { updated, targetName };
};
// Try modern (V1), then modern (V2); finally v4 as safety
let out, mode = “modern”;
try {
out = await putWithParams(paramsModernV1, 6);
} catch {
try {
out = await putWithParams(paramsModernV2, 6);
} catch {
mode = “v4”;
out = await putWithParams(paramsV4, 4);
}
}
return res.json({
workflowId: out.updated.id,
nodeName: out.targetName,
modeUsed: mode, // “modern” or “v4”
sheetName,
usedDocumentUrl: canonicalUrl,
});
} catch (e) {
console.error(e);
res.status(500).json({ error: String(e.message || e) });
}
});
HERE. , all configurations are getting except that document mode needs to select as By URL
