AutomationFlowsGeneral › Sync Linear Tickets to Google Sheets Daily

Sync Linear Tickets to Google Sheets Daily

Original n8n title: Write All Linear Tickets to Google Sheets

Write All Linear Tickets To Google Sheets. Uses scheduleTrigger, graphql, stickyNote, splitOut. Scheduled trigger; 14 nodes.

Cron / scheduled trigger★★★★☆ complexity14 nodesGraphQLGoogle Sheets
General Trigger: Cron / scheduled Nodes: 14 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "nodes": [
    {
      "id": "58c69358-3d02-41c3-b50c-6e57452523a2",
      "name": "Every day at 06:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        960,
        600
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0a388351-6aec-45c4-a142-e89b338be155",
      "name": "Get all your team's tickets",
      "type": "n8n-nodes-base.graphql",
      "position": [
        1200,
        600
      ],
      "parameters": {
        "query": "query ($filter: IssueFilter) {\n  issues(filter: $filter, first: 100) {\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n    nodes {\n      id\n      identifier\n      url\n      title\n      priorityLabel\n      createdAt\n      completedAt\n      state {\n        type\n        name\n      }\n      cycle {\n        number\n      }\n      estimate\n      labels { nodes { name } }\n    }\n  }\n}",
        "endpoint": "https://api.linear.app/graphql",
        "variables": "={\n  \"filter\": {\n    \"team\": {\n      \"name\":  {\n        \"eq\": \"Adore\"\n      }\n    }\n  }\n}",
        "requestFormat": "json",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "91f29056-f934-4c15-8e98-2a202753971d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        460
      ],
      "parameters": {
        "color": 7,
        "width": 256.14371825927645,
        "height": 100,
        "content": "\ud83d\udc47\ud83c\udffd Set your team name here in the filter. \n(Our team's name is Adore)"
      },
      "typeVersion": 1
    },
    {
      "id": "8efeb08c-fbba-4c07-9ef8-e22afa39f328",
      "name": "if has next page",
      "type": "n8n-nodes-base.if",
      "position": [
        1400,
        780
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f5ab21aa-b2e0-4885-9278-6756c2c544f9",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.issues.pageInfo.hasNextPage }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "fdfba048-ae14-45fb-88be-e354d7003fdb",
      "name": "Get end cursor",
      "type": "n8n-nodes-base.set",
      "position": [
        1620,
        920
      ],
      "parameters": {
        "fields": {
          "values": [
            {
              "name": "after",
              "stringValue": "={{ $json.data.issues.pageInfo.endCursor }}"
            }
          ]
        },
        "include": "none",
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "db37b327-d939-4a8b-bd5c-ff99c2191a54",
      "name": "Get next page",
      "type": "n8n-nodes-base.graphql",
      "position": [
        1880,
        920
      ],
      "parameters": {
        "query": "=query ($filter: IssueFilter) {\n  issues(filter: $filter, first: 100, after: \"{{ $json.after }}\") {\n    nodes {\n      id\n      identifier\n      url\n      title\n      priorityLabel\n      createdAt\n      completedAt\n      state {\n        type\n        name\n      }\n      cycle {\n        number\n      }\n      estimate\n      labels { nodes { name } }\n    }\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n  }\n}",
        "endpoint": "https://api.linear.app/graphql",
        "variables": "={\n  \"filter\": {\n    \"team\": {\n      \"name\":  {\n        \"eq\": \"Adore\"\n      }\n    }\n  }\n}",
        "requestFormat": "json",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f7857473-966e-433e-a7d7-9c4e4bdb670a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2060,
        940
      ],
      "parameters": {
        "color": 7,
        "width": 256.14371825927645,
        "height": 100,
        "content": "\ud83d\udc48\ud83c\udffd Set your team name here in the filter. \n(Our team's name is Adore)"
      },
      "typeVersion": 1
    },
    {
      "id": "75365be8-d111-4bc8-9da8-77e8d3a1de6c",
      "name": "Split out the tickets",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2020,
        600
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data.issues.nodes"
      },
      "typeVersion": 1
    },
    {
      "id": "691a8299-9c96-4520-a56b-7edf3bee42b5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2240,
        460
      ],
      "parameters": {
        "color": 7,
        "width": 256.14371825927645,
        "height": 100,
        "content": "\ud83d\udc47\ud83c\udffd Adjust any custom fields. Here we set labels and default estimate of 1"
      },
      "typeVersion": 1
    },
    {
      "id": "4ff13ada-6534-4171-8704-4917b247727d",
      "name": "Set custom fields",
      "type": "n8n-nodes-base.set",
      "position": [
        2240,
        600
      ],
      "parameters": {
        "fields": {
          "values": [
            {
              "name": "estimate",
              "type": "numberValue",
              "numberValue": "={{ $json.estimate ?? 1 }}"
            },
            {
              "name": "labels",
              "stringValue": "={{ $json.labels.nodes.map((label) => label.name).toString() }}"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "02577d21-656e-425b-9924-37a34c822fe8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        300
      ],
      "parameters": {
        "color": 5,
        "width": 403.45318928152614,
        "height": 280.9004675550071,
        "content": "### \ud83d\udc68\u200d\ud83c\udfa4 Setup\n1. Add Linear API header key\n2. Add Google sheets creds\n3. Update which teams to get tickets from in Graphql Nodes\n4. Update which Google Sheets page to write all the tickets to. \n **You only need to add one column, id. Google Sheets node in automatic mapping mode will handle adding the rest of the columns.**\n5. Set any custom data on each ticket\n6. Activate workflow \ud83d\ude80"
      },
      "typeVersion": 1
    },
    {
      "id": "6344a764-20f0-4f46-bdc8-1599c2f9ba95",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2700,
        780
      ],
      "parameters": {
        "color": 7,
        "width": 256.14371825927645,
        "height": 100,
        "content": "\ud83d\udc46\ud83c\udffd Update which Google sheet to write to"
      },
      "typeVersion": 1
    },
    {
      "id": "ba08e0c7-1607-4bcd-8b37-99a6145b0cba",
      "name": "Write tickets to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2720,
        600
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 2072772685,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pQxSIZ2dSoA8Fmr3B4EId9VOQXH1hVuOZgCHxcaKN7k/edit#gid=2072772685",
          "cachedResultName": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1pQxSIZ2dSoA8Fmr3B4EId9VOQXH1hVuOZgCHxcaKN7k",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pQxSIZ2dSoA8Fmr3B4EId9VOQXH1hVuOZgCHxcaKN7k/edit?usp=drivesdk",
          "cachedResultName": "Adore tickets"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 5000
    },
    {
      "id": "df3ae898-1f09-4be3-a96a-f1f9cba58730",
      "name": "Flatten object to have simple fields to filter by",
      "type": "n8n-nodes-base.code",
      "position": [
        2480,
        600
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function flattenObject(ob) {\n    var toReturn = {};\n\n    for (var i in ob) {\n        if (!ob.hasOwnProperty(i)) continue;\n\n        if ((typeof ob[i]) == 'object' && ob[i] !== null) {\n            var flatObject = flattenObject(ob[i]);\n            for (var x in flatObject) {\n                if (!flatObject.hasOwnProperty(x)) continue;\n\n                toReturn[i + '.' + x] = flatObject[x];\n            }\n        } else {\n            toReturn[i] = ob[i];\n        }\n    }\n    return toReturn;\n}\n\nreturn flattenObject($input.item.json);"
      },
      "typeVersion": 2
    }
  ],
  "connections": {
    "Get next page": {
      "main": [
        [
          {
            "node": "if has next page",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split out the tickets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get end cursor": {
      "main": [
        [
          {
            "node": "Get next page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "if has next page": {
      "main": [
        [
          {
            "node": "Get end cursor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set custom fields": {
      "main": [
        [
          {
            "node": "Flatten object to have simple fields to filter by",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every day at 06:00": {
      "main": [
        [
          {
            "node": "Get all your team's tickets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split out the tickets": {
      "main": [
        [
          {
            "node": "Set custom fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all your team's tickets": {
      "main": [
        [
          {
            "node": "Split out the tickets",
            "type": "main",
            "index": 0
          },
          {
            "node": "if has next page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten object to have simple fields to filter by": {
      "main": [
        [
          {
            "node": "Write tickets to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

Effortlessly synchronise your team's Linear tickets into a Google Sheets spreadsheet each morning, eliminating manual data entry and providing a centralised view for reporting or analysis. This workflow suits project managers or teams relying on Linear for issue tracking who need regular exports to Sheets for collaboration or metrics tracking. The key step involves a GraphQL query to fetch all tickets with pagination, ensuring complete data capture without omissions.

Use this workflow for daily overviews of ticket statuses in a familiar spreadsheet format, particularly when integrating with tools like Google Workspace for broader team access. Avoid it for real-time updates, as the scheduled trigger runs only once a day; opt instead for event-based triggers in dynamic environments. Common variations include filtering tickets by status or assignee before export, or appending data to historical sheets for trend analysis.

About this workflow

Write All Linear Tickets To Google Sheets. Uses scheduleTrigger, graphql, stickyNote, splitOut. Scheduled trigger; 14 nodes.

Source: https://github.com/Zie619/n8n-workflows — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

General

Add Liked Songs To A Spotify Monthly Playlist. Uses stickyNote, spotify, nocoDb, noOp. Scheduled trigger; 30 nodes.

Spotify, Noco Db
General

&gt; This Workflow is a port of Add saved songs to a monthly playlist from IFTTT.

Spotify, Noco Db
General

Shopify to Google Sheets Product Sync Automation. Uses noOp, graphql, googleSheets, scheduleTrigger. Scheduled trigger; 25 nodes.

GraphQL, Google Sheets
General

This automation template allows you to automatically receive news from RSS feeds, process their content, and publish or schedule posts on various social media platforms using PostPulse.

@Postpulse/N8N Nodes Postpulse, RSS Feed Read
General

Send Google Analytics Data To A I To Analyze Then Save Results In Baserow. Uses scheduleTrigger, manualTrigger, stickyNote, googleAnalytics. Scheduled trigger; 22 nodes.

Google Analytics, HTTP Request, Baserow