{
  "name": "Generate label PDFs from Google Sheets to Drive with SheetsToLabels",
  "nodes": [
    {
      "parameters": {
        "content": "## Generate label PDFs from Google Sheets to Drive with SheetsToLabels\n\n**Who’s it for**\nTeams that keep label or badge rows in Google Sheets and want each ready row turned into a print-ready PDF stored in Google Drive.\n\n**How it works / What it does**\n1. **Set config** holds the SheetsToLabels `templateId`, layout, Drive folder ID, and spreadsheet placeholders.\n2. **Read Google Sheet** loads rows from your spreadsheet.\n3. **Build API body** maps sheet columns to template variables and builds a stable `Idempotency-Key`.\n4. **Generate PDF** calls SheetsToLabels and returns binary PDF under `data`.\n5. **Upload PDF to Drive** stores the file in your chosen folder.\n\n**How to set up**\n1. Create HTTP Header Auth: name `Authorization`, value `Bearer sk-your-api-key` ([API keys](https://sheetstolabels.com/settings/apikeys)).\n2. Connect Google Sheets + Google Drive OAuth credentials.\n3. Sync a Label Designer template to the cloud; paste `templateId` into **Set config**.\n4. Replace spreadsheet / sheet / Drive folder placeholders in **Set config**, then select them in the Google nodes.\n5. Align sheet column headers with template variable names (or edit mapping in **Build API body**).\n6. Test with one row, then add a Schedule or Sheets Trigger.\n\n**Requirements**\n- SheetsToLabels Business plan or API trial\n- Cloud-synced template for the same API key account\n- Google Sheets + Google Drive OAuth in n8n\n\n**How to customize**\n- Batch multiple rows into one `records` array for Avery-style sheets.\n- Set `layoutType` to `preset` and choose a `presetId`.\n- Write the Drive file URL back onto the sheet after upload.\n- Docs: https://sheetstolabels.com/docs/api/integrations/n8n",
        "height": 700,
        "width": 480,
        "color": 5
      },
      "id": "sticky-main-gs",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-980, -300]
    },
    {
      "parameters": {
        "content": "### Step 1 — Trigger + config\nEdit placeholders only in **Set config**. Swap Manual Trigger for Schedule / Sheets Trigger later.",
        "height": 130,
        "width": 260,
        "color": 7
      },
      "id": "sticky-gs-1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-560, 280]
    },
    {
      "parameters": {
        "content": "### Step 2 — Read sheet\nPoint at your spreadsheet. Prefer a ready-status filter so unfinished rows are skipped.",
        "height": 120,
        "width": 240,
        "color": 7
      },
      "id": "sticky-gs-2",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-240, 280]
    },
    {
      "parameters": {
        "content": "### Step 3 — Map + generate\nMaps columns → variables, calls SheetsToLabels. Keep response format = file. Never put the API key in the URL/body.",
        "height": 140,
        "width": 280,
        "color": 7
      },
      "id": "sticky-gs-3",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [80, 280]
    },
    {
      "parameters": {
        "content": "### Step 4 — Upload\nUses binary `data`. Folder ID comes from **Set config**.",
        "height": 110,
        "width": 220,
        "color": 7
      },
      "id": "sticky-gs-4",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [460, 280]
    },
    {
      "parameters": {},
      "id": "gs-trigger",
      "name": "When clicking Test workflow",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [-520, 40]
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "gs-cfg-template",
              "name": "templateId",
              "value": "custom-REPLACE_ME",
              "type": "string"
            },
            {
              "id": "gs-cfg-layout",
              "name": "layoutType",
              "value": "single",
              "type": "string"
            },
            {
              "id": "gs-cfg-preset",
              "name": "presetId",
              "value": "avery-5160-compatible",
              "type": "string"
            },
            {
              "id": "gs-cfg-spreadsheet",
              "name": "spreadsheetId",
              "value": "REPLACE_SPREADSHEET_ID",
              "type": "string"
            },
            {
              "id": "gs-cfg-sheet",
              "name": "sheetName",
              "value": "REPLACE_SHEET_NAME",
              "type": "string"
            },
            {
              "id": "gs-cfg-folder",
              "name": "driveFolderId",
              "value": "REPLACE_DRIVE_FOLDER_ID",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "gs-set-config",
      "name": "Set config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [-280, 40]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set config').item.json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set config').item.json.sheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "id": "gs-read-sheet",
      "name": "Read Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [-40, 40],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const cfg = $('Set config').first().json;\nconst item = $input.first().json;\nconst rowId = String(item.row_number ?? item.id ?? item.Name ?? Date.now());\nconst updatedAt = String(item.updatedAt ?? item.Updated ?? 'v1');\n\nconst layout =\n  cfg.layoutType === 'preset'\n    ? { type: 'preset', presetId: cfg.presetId }\n    : { type: 'single' };\n\nconst variables = {\n  name: item.name ?? item.Name ?? '',\n  code: item.code ?? item.Code ?? '',\n  qrUrl: item.qrUrl ?? item.QR ?? item.Link ?? ''\n};\n\nreturn [{\n  json: {\n    body: {\n      templateId: cfg.templateId,\n      layout,\n      records: [{ variables }]\n    },\n    idempotencyKey: `gsheet-${rowId}-${updatedAt}`,\n    fileName: `label-${rowId}.pdf`,\n    driveFolderId: cfg.driveFolderId\n  }\n}];"
      },
      "id": "gs-build-body",
      "name": "Build API body",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [200, 40]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://sheetstolabels.com/api/v1/generate-pdf",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ $json.idempotencyKey }}"
            },
            {
              "name": "X-Integration-Source",
              "value": "n8n"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json.body) }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "id": "gs-generate-pdf",
      "name": "Generate PDF",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [440, 40],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "SheetsToLabels API Header Auth"
        }
      }
    },
    {
      "parameters": {
        "name": "={{ $('Build API body').item.json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $('Build API body').item.json.driveFolderId }}",
          "mode": "id"
        },
        "options": {
          "binaryPropertyName": "data"
        }
      },
      "id": "gs-upload-drive",
      "name": "Upload PDF to Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [680, 40],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "",
          "name": "Google Drive account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "When clicking Test workflow": {
      "main": [[{ "node": "Set config", "type": "main", "index": 0 }]]
    },
    "Set config": {
      "main": [[{ "node": "Read Google Sheet", "type": "main", "index": 0 }]]
    },
    "Read Google Sheet": {
      "main": [[{ "node": "Build API body", "type": "main", "index": 0 }]]
    },
    "Build API body": {
      "main": [[{ "node": "Generate PDF", "type": "main", "index": 0 }]]
    },
    "Generate PDF": {
      "main": [[{ "node": "Upload PDF to Drive", "type": "main", "index": 0 }]]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
