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
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
