This workflow follows the Google Drive → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "GW-Admin / Core / One-time Setup",
"nodes": [
{
"parameters": {},
"id": "trigger",
"name": "Manual",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-1000,
0
]
},
{
"parameters": {
"content": "## One-time Setup (native nodes)\n\nRun this ONCE before activating any other workflow.\n\nWhat it does (matches `setupAdminWorkflow` minus form creation):\n\n1. Ensures these subfolders exist under your `ROOT_FOLDER_ID` (Google Drive nodes):\n - `01_\u516c\u6587\u5c08\u6848\u555f\u52d5\u8868`\n - `02_\u5c08\u6848\u7e3d\u63a7\u8868`\n - `03_\u5c08\u6848\u8cc7\u6599\u593e`\n - `04_\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u65b0\u589e\u8868`\n - `06_Apps Script`\n2. Creates `\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868` spreadsheet with sheets `\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868` and `\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u7d00\u9304` (Google Sheets node).\n3. Appends header rows to both sheets (Google Sheets append).\n4. Applies bold + light-blue formatting + frozen row to both header rows \u26a0\ufe0f via HTTP `spreadsheets.batchUpdate`.\n5. Moves the control sheet into `02_\u5c08\u6848\u7e3d\u63a7\u8868` (Google Drive move).\n6. Returns: `{ CONTROL_SHEET_ID, CONTROL_SHEET_URL, projectsParentFolderId }`.\n\nBefore running: fill the **Config** node with your `ROOT_FOLDER_ID`.",
"height": 480,
"width": 540,
"color": 4
},
"id": "sticky-overview",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1000,
200
]
},
{
"parameters": {
"jsCode": "// Fill in ROOT_FOLDER_ID before running.\nconst config = {\n ROOT_FOLDER_ID: 'YOUR_GOOGLE_DRIVE_ROOT_FOLDER_ID',\n TIMEZONE: 'Asia/Taipei'\n};\nif (!config.ROOT_FOLDER_ID || config.ROOT_FOLDER_ID.startsWith('YOUR_')) {\n throw new Error('\u8acb\u5148\u5728 Config \u7bc0\u9ede\u586b\u5165 ROOT_FOLDER_ID');\n}\nconst ROOT_SUBFOLDERS = [\n '01_\u516c\u6587\u5c08\u6848\u555f\u52d5\u8868',\n '02_\u5c08\u6848\u7e3d\u63a7\u8868',\n '03_\u5c08\u6848\u8cc7\u6599\u593e',\n '04_\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u65b0\u589e\u8868',\n '06_Apps Script'\n];\nconst controlHeaders = ['\u5c08\u6848\u7de8\u865f','\u5c08\u6848\u540d\u7a31','\u5e74\u5ea6','\u627f\u8fa6\u8655\u5ba4','\u627f\u8fa6\u4eba','\u627f\u8fa6\u4ebaEmail','Drive\u8cc7\u6599\u593e\u9023\u7d50','\u5c08\u6848\u7d00\u9304Docs\u9023\u7d50','\u5f85\u8fa6\u8ffd\u8e64\u8868\u9023\u7d50','\u6210\u679c\u6aa2\u6838\u8868\u9023\u7d50','NotebookLM\u7b46\u8a18\u672c\u9023\u7d50','\u5c08\u6848\u72c0\u614b','\u5099\u8a3b','\u4f86\u6587\u55ae\u4f4d','\u516c\u6587\u6587\u865f','\u5efa\u7acb\u65e5\u671f','\u6d3b\u52d5\u65e5\u671f','\u6210\u679c\u671f\u9650','\u7d93\u8cbb\u671f\u9650','\u662f\u5426\u6709\u7d93\u8cbb'];\nconst milestoneHeaders = ['\u7d00\u9304\u7de8\u865f','\u65b0\u589e\u6642\u9593','\u5efa\u7acb\u8005Email','\u5c08\u6848\u7de8\u865f','\u5c08\u6848\u540d\u7a31','\u65e5\u671f\u985e\u578b','\u65e5\u671f','\u63d0\u9192\u8a2d\u5b9a','\u8ca0\u8cac\u4eba','\u8ca0\u8cac\u4ebaEmail','\u662f\u5426\u5beb\u5165\u5f85\u8fa6\u8ffd\u8e64\u8868','\u662f\u5426\u5efa\u7acbCalendar\u63d0\u9192','\u8aaa\u660e','Calendar\u4e8b\u4ef6ID','\u5c0d\u61c9\u5f85\u8fa6\u8ffd\u8e64\u8868\u9023\u7d50','\u72c0\u614b','\u5099\u8a3b'];\nfunction headerRow(headers) { const o={}; for (const h of headers) o[h]=h; return o; }\nreturn [{ json: { config, ROOT_SUBFOLDERS, controlHeaders, milestoneHeaders, controlHeaderRow: headerRow(controlHeaders), milestoneHeaderRow: headerRow(milestoneHeaders) } }];\n"
},
"id": "config",
"name": "Config",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-760,
0
]
},
{
"parameters": {
"jsCode": "const { config, ROOT_SUBFOLDERS } = $input.first().json;\nconst base = $input.first().json;\nreturn ROOT_SUBFOLDERS.map(name => ({ json: { ...base, folderName: name } }));\n"
},
"id": "explode",
"name": "Explode subfolders",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-520,
0
]
},
{
"parameters": {
"resource": "fileFolder",
"queryString": "=name='{{ $json.folderName }}' and mimeType='application/vnd.google-apps.folder' and '{{ $json.config.ROOT_FOLDER_ID }}' in parents and trashed=false",
"filter": {
"driveScope": "user"
},
"options": {}
},
"id": "search-folder",
"name": "Search subfolder",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
-280,
0
],
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "exists",
"leftValue": "={{ $items().length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-missing",
"name": "If missing?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-40,
0
]
},
{
"parameters": {
"resource": "folder",
"name": "={{ $('Explode subfolders').item.json.folderName }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Explode subfolders').item.json.config.ROOT_FOLDER_ID }}"
},
"options": {}
},
"id": "create-missing",
"name": "Create missing folder",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
200,
-160
],
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Merge results \u2014 each item is either an existing search hit or a freshly-created folder.\nconst all = $input.all();\nconst byName = {};\nfor (const it of all) {\n const j = it.json;\n if (j.name && j.id) byName[j.name] = j.id;\n}\nconst config = $('Config').first().json.config;\nconst base = $('Config').first().json;\nreturn [{ json: { ...base, rootSubfolderIds: byName, controlFolderId: byName['02_\u5c08\u6848\u7e3d\u63a7\u8868'] } }];\n"
},
"id": "merge-rootfolders",
"name": "Collect root folders",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
0
]
},
{
"parameters": {
"resource": "spreadsheet",
"operation": "create",
"title": "\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868",
"sheetsUi": {
"sheetValues": [
{
"title": "\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868"
},
{
"title": "\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u7d00\u9304"
}
]
},
"options": {}
},
"id": "create-control-sheet",
"name": "Create \u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
680,
0
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const created = $input.first().json;\nconst base = $('Collect root folders').first().json;\nreturn [{ json: { ...base, controlSheetId: created.spreadsheetId, controlSheetUrl: created.spreadsheetUrl || `https://docs.google.com/spreadsheets/d/${created.spreadsheetId}/edit` } }];\n"
},
"id": "carry-control",
"name": "Carry control sheet id",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
920,
0
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.controlSheetId }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868"
},
"dataMode": "autoMapInputData",
"fieldsUi": {
"fieldValues": []
},
"options": {}
},
"id": "header-control",
"name": "Write \u7e3d\u63a7\u8868 header row",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1160,
0
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Feed header row as data items so autoMapInputData writes a row with header names as values.\nconst prep = $('Carry control sheet id').first().json;\nreturn [{ json: { ...prep, ...prep.controlHeaderRow } }];\n"
},
"id": "feed-control-headers",
"name": "Feed \u7e3d\u63a7\u8868 header data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1160,
-200
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Carry control sheet id').first().json.controlSheetId }}"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u7d00\u9304"
},
"dataMode": "autoMapInputData",
"fieldsUi": {
"fieldValues": []
},
"options": {}
},
"id": "header-milestone",
"name": "Write \u968e\u6bb5\u7d00\u9304 header row",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1400,
0
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const prep = $('Carry control sheet id').first().json;\nreturn [{ json: { ...prep, ...prep.milestoneHeaderRow } }];\n"
},
"id": "feed-milestone-headers",
"name": "Feed \u968e\u6bb5\u7d00\u9304 header data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1400,
-200
]
},
{
"parameters": {
"content": "### \u26a0\ufe0f HTTP fallback \u2014 Sheets header bold + bg colour + frozen row\n\nNeeded because the native Google Sheets node has no operation for `repeatCell` (header formatting) or `updateSheetProperties` (frozen rows).\n\nThis call applies the standard light-blue bold header to BOTH sheets (`\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868` + `\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u7d00\u9304`) in one batchUpdate. Same fallback pattern as the project-starter core.",
"height": 280,
"width": 480,
"color": 5
},
"id": "sticky-format-http",
"name": "Why HTTP: Header format",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1640,
-360
]
},
{
"parameters": {
"jsCode": "// Build a batchUpdate body that needs the two sheets' internal numeric sheetIds.\n// We need the actual sheetIds (Google internally assigns them). Fetch via Drive? No \u2014\n// use the Sheets API metadata: a separate GET would be cleaner; instead we encode by index.\n// In the freshly-created spreadsheet, sheet 0 = \u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868, sheet 1 = \u5c08\u6848\u968e\u6bb5\u65e5\u671f\u7d00\u9304\n// but Google sometimes assigns non-zero ids. Safer: refer by sheet *title*.\nconst prep = $('Carry control sheet id').first().json;\n// We'll use a small lookup pass via Sheets metadata in the next node.\nreturn [{ json: prep }];\n"
},
"id": "before-format",
"name": "Prep format body",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1640,
0
]
},
{
"parameters": {
"method": "GET",
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.controlSheetId }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleApi",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "fields",
"value": "sheets(properties(sheetId,title))"
}
]
},
"options": {}
},
"id": "get-sheet-ids",
"name": "Get sheet numeric ids (HTTP)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1880,
0
],
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const prep = $('Carry control sheet id').first().json;\nconst meta = $input.first().json;\nconst byTitle = {};\nfor (const s of (meta.sheets || [])) byTitle[s.properties.title] = s.properties.sheetId;\nconst controlSheetIdNum = byTitle['\u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868'];\nconst milestoneSheetIdNum = byTitle['\u5c08\u6848\u968e\u6bb5\u65e5\u671f\u7d00\u9304'];\nfunction headerFormatRequests(sheetId, columnCount) {\n return [\n { repeatCell: { range: { sheetId, startRowIndex: 0, endRowIndex: 1, startColumnIndex: 0, endColumnIndex: columnCount }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.92, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat(textFormat,backgroundColor)' } },\n { updateSheetProperties: { properties: { sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } }\n ];\n}\nconst body = { requests: [\n ...headerFormatRequests(controlSheetIdNum, prep.controlHeaders.length),\n ...headerFormatRequests(milestoneSheetIdNum, prep.milestoneHeaders.length)\n]};\nreturn [{ json: { ...prep, _formatBody: body } }];\n"
},
"id": "build-format-body",
"name": "Build format batchUpdate body",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2120,
0
]
},
{
"parameters": {
"method": "POST",
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.controlSheetId }}:batchUpdate",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json._formatBody) }}",
"options": {}
},
"id": "format-headers",
"name": "Apply header formatting (HTTP)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2360,
0
],
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "file",
"operation": "move",
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Carry control sheet id').first().json.controlSheetId }}"
},
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Carry control sheet id').first().json.controlFolderId }}"
}
},
"id": "move-control",
"name": "Move control sheet to 02",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
2600,
0
],
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const prep = $('Carry control sheet id').first().json;\nreturn [{\n json: {\n ok: true,\n CONTROL_SHEET_ID: prep.controlSheetId,\n CONTROL_SHEET_URL: prep.controlSheetUrl,\n rootSubfolderIds: prep.rootSubfolderIds,\n nextSteps: [\n '1. \u628a\u4e0a\u9762\u7684 CONTROL_SHEET_ID \u586b\u56de entry-* workflow \u7684 Config \u7bc0\u9ede\u3002',\n '2. \u4e4b\u5f8c\u9001\u51fa\u516c\u6587\u5c08\u6848\u555f\u52d5\u8868\u6703\u81ea\u52d5\u5beb\u5165\u9019\u5f35\u7e3d\u63a7\u8868\u3002',\n '3. \u82e5\u4f7f\u7528 Google Forms \u5165\u53e3\uff0c\u8a18\u5f97\u628a apps-script-bridge.gs \u90e8\u7f72\u5230\u5c0d\u61c9\u7684 Form \u8a66\u7b97\u8868\u3002'\n ]\n }\n}];\n"
},
"id": "summary",
"name": "Return setup result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2840,
0
]
}
],
"connections": {
"Manual": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Explode subfolders",
"type": "main",
"index": 0
}
]
]
},
"Explode subfolders": {
"main": [
[
{
"node": "Search subfolder",
"type": "main",
"index": 0
}
]
]
},
"Search subfolder": {
"main": [
[
{
"node": "If missing?",
"type": "main",
"index": 0
}
]
]
},
"If missing?": {
"main": [
[
{
"node": "Create missing folder",
"type": "main",
"index": 0
}
],
[
{
"node": "Collect root folders",
"type": "main",
"index": 0
}
]
]
},
"Create missing folder": {
"main": [
[
{
"node": "Collect root folders",
"type": "main",
"index": 0
}
]
]
},
"Collect root folders": {
"main": [
[
{
"node": "Create \u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868",
"type": "main",
"index": 0
}
]
]
},
"Create \u884c\u653f\u5c08\u6848\u7e3d\u63a7\u8868": {
"main": [
[
{
"node": "Carry control sheet id",
"type": "main",
"index": 0
}
]
]
},
"Carry control sheet id": {
"main": [
[
{
"node": "Feed \u7e3d\u63a7\u8868 header data",
"type": "main",
"index": 0
}
]
]
},
"Feed \u7e3d\u63a7\u8868 header data": {
"main": [
[
{
"node": "Write \u7e3d\u63a7\u8868 header row",
"type": "main",
"index": 0
}
]
]
},
"Write \u7e3d\u63a7\u8868 header row": {
"main": [
[
{
"node": "Feed \u968e\u6bb5\u7d00\u9304 header data",
"type": "main",
"index": 0
}
]
]
},
"Feed \u968e\u6bb5\u7d00\u9304 header data": {
"main": [
[
{
"node": "Write \u968e\u6bb5\u7d00\u9304 header row",
"type": "main",
"index": 0
}
]
]
},
"Write \u968e\u6bb5\u7d00\u9304 header row": {
"main": [
[
{
"node": "Prep format body",
"type": "main",
"index": 0
}
]
]
},
"Prep format body": {
"main": [
[
{
"node": "Get sheet numeric ids (HTTP)",
"type": "main",
"index": 0
}
]
]
},
"Get sheet numeric ids (HTTP)": {
"main": [
[
{
"node": "Build format batchUpdate body",
"type": "main",
"index": 0
}
]
]
},
"Build format batchUpdate body": {
"main": [
[
{
"node": "Apply header formatting (HTTP)",
"type": "main",
"index": 0
}
]
]
},
"Apply header formatting (HTTP)": {
"main": [
[
{
"node": "Move control sheet to 02",
"type": "main",
"index": 0
}
]
]
},
"Move control sheet to 02": {
"main": [
[
{
"node": "Return setup result",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"active": false
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleApigoogleDriveOAuth2ApigoogleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
GW-Admin / Core / One-time Setup. Uses googleDrive, googleSheets, httpRequest. Event-driven trigger; 21 nodes.
Source: https://github.com/MorrisLu-Taipei/TigerAI-n8n-Skill-Pack/blob/e98f34af0b1499436c114a82a395e76ab5533c84/examples/google-workspace-admin-workflow/core/core-setup.workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
PCN. Uses googleSheets, httpRequest, @n-octo-n/n8n-nodes-json-database, itemLists. Event-driven trigger; 60 nodes.
The workflow automates the process of gathering extensive keyword data for a "Main Keyword." It starts by reading initial parameters from a Google Sheets template, creates a new dedicated Google Sheet
🔥 March Sale – n8n Community Members Get ideoGener8r for Just $27! (Reg. $47) Use Coupon Code: (Valid until 3/31/2025 for n8n community members)
📄 Documentation: Notion Guide
Overview