从 Notion 内容创建 Linear 工单
高级
这是一个Engineering, Product领域的自动化工作流,包含 24 个节点。主要使用 If, Set, Code, Filter, Linear 等节点。 从 Notion 内容创建 Linear 工单
前置要求
- •Notion API Key
- •可能需要目标 API 的认证凭证
- •OpenAI API Key
- •HTTP Webhook 端点(n8n 会自动生成)
使用的节点 (24)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"meta": {
"instanceId": "cb484ba7b742928a2048bf8829668bed5b5ad9787579adea888f05980292a4a7"
},
"nodes": [
{
"id": "7d11aa76-c7bf-4aa3-9f94-fb2231f5055b",
"name": "遍历项目",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-1460,
2080
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "bea6febe-077f-4b90-887c-9c82954ef5d9",
"name": "获取 Linear 团队详情",
"type": "n8n-nodes-base.graphql",
"position": [
-1220,
1760
],
"parameters": {
"query": "=query GetTeamsAndProjects {\n teams(filter: {name: {contains: \"{{ $json['Linear team name'] }}\"}}) {\n nodes {\n id\n name\n members {\n nodes {\n id\n name\n email\n }\n }\n projects {\n nodes {\n id\n name\n description\n }\n }\n }\n }\n}\n",
"endpoint": "https://api.linear.app/graphql",
"requestMethod": "GET",
"authentication": "headerAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "zYILrk4RKFqdP66s",
"name": "[Omar] Notion credentials for GraphQL API"
}
},
"executeOnce": true,
"typeVersion": 1,
"continueOnFail": true
},
{
"id": "27a2111b-716b-4150-af92-ab90d7e83642",
"name": "获取问题内容",
"type": "n8n-nodes-base.notion",
"position": [
-1220,
2340
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Set assignee and title').item.json.id }}"
},
"resource": "block",
"operation": "getAll",
"returnAll": true,
"simplifyOutput": false,
"fetchNestedBlocks": true
},
"credentials": {
"notionApi": {
"id": "80",
"name": "Notion david-internal"
}
},
"typeVersion": 2.1,
"alwaysOutputData": true
},
{
"id": "96bde9b1-b84e-445a-b90c-6dd4031076aa",
"name": "聚合",
"type": "n8n-nodes-base.aggregate",
"position": [
-560,
2340
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "markdown"
}
]
}
},
"typeVersion": 1
},
{
"id": "45c63a2d-d457-4882-ac99-4b8e4a63ae43",
"name": "准备问题数据",
"type": "n8n-nodes-base.set",
"position": [
-1220,
2620
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e1b44489-ee32-4da4-816e-f56d640a9731",
"name": "title",
"type": "string",
"value": "={{ $if($('Set assignee and title').item.json.title.length <= 70, $('Set assignee and title').item.json.title, $('Shorten title').item.json.message.content) }}"
},
{
"id": "f3fab4f6-8ea3-4b93-91ea-ec08c2d9eded",
"name": "description",
"type": "string",
"value": "=_Issue created automatically from a [Notion block]({{ $('Set page URL').last().json.page_url + '?pvs=4#' + $('Loop Over Items').item.json.id.replaceAll('-', '') }})_ {{ $if($('Set assignee and title').item.json.assignee_fragment && !$('Set assignee and title').item.json.assignee, \"\\nAssignee '\" + $('Set assignee and title').item.json.assignee_fragment + \"' not found\", '') }}\n\n{{ $json.markdown?.join('\\n') }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "ce619c7d-8363-48ec-86c8-a1f16097eb3e",
"name": "创建 Linear 问题",
"type": "n8n-nodes-base.linear",
"position": [
-1000,
2620
],
"parameters": {
"title": "={{ $json.title }}",
"teamId": "={{ $('Set team ID').item.json.team_id }}",
"additionalFields": {
"assigneeId": "={{ $('Set assignee and title').item.json.assignee.id }}",
"description": "={{ $json.description }}"
}
},
"credentials": {
"linearApi": {
"id": "218",
"name": "Linear account (David)"
}
},
"typeVersion": 1
},
{
"id": "cc365fa1-a2cc-430c-8cb4-5d708b7a66b9",
"name": "设置分配人和标题",
"type": "n8n-nodes-base.code",
"position": [
-1220,
2080
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Set the title and the assignee based on the first line of the item\n\nlet firstLine = $json[$json.type].text.reduce((s, o) => {\n return s + o.text.content\n}, \"\")\nconsole.log('firstLin', firstLine)\nconst regex = /^(\\[[^\\]]*\\]\\s)?(.+)$/;\nconst match = firstLine.match(regex);\nconsole.log('match', match)\n\nif (match) {\n // If the first part is not present, match[1] will be undefined\n item.json.assignee_fragment = match[1]?.slice(1, -2) || null;\n item.json.title = match[2];\n} else {\n item.json.title = firstLine;\n item.json.assignee_fragment = null;\n}\n\n// Set the new title in Notion format\n// $url will be set later, once we have it\nconst prefix_link = [\n {\"text\":{\"content\":\"[\"}},\n {\"text\":{\"content\":\"In Linear\", \"link\":{\"url\": \"$url\"} }},\n {\"text\":{\"content\":\"] \"}}\n]\nitem.json.new_content = {\n \"rich_text\": [...prefix_link, ...item.json.to_do.text]\n}\n\n// Find a matching assignee\nconst members = $('Fetch Linear team details').item.json.data.teams.nodes[0].members.nodes\nconsole.log('people', members)\nconsole.log('fragment', item.json.assignee_fragment)\nconst matching_people = members.filter(p => \n p.name.toLowerCase().startsWith(item.json.assignee_fragment?.toLowerCase())\n)\nconsole.log('mpeople', matching_people)\nif (matching_people.length > 0) {\n item.json.assignee = matching_people[0]\n}\n\nitem.pairedItem = 0\n\nreturn item"
},
"typeVersion": 2
},
{
"id": "15d9c526-95f3-4209-9a9f-a0ba4c09a67e",
"name": "团队缺失?",
"type": "n8n-nodes-base.if",
"position": [
-1000,
1760
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "047fbe62-ebab-44ab-89b1-232f5f15874d",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.data.teams?.nodes?.length < 1 }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "491a1176-18a7-442a-8f64-a8c946ac25dc",
"name": "设置页面 URL",
"type": "n8n-nodes-base.set",
"position": [
-1000,
2340
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "0b0ced59-14c9-43e9-a5ee-f4b1862fccd6",
"name": "page_url",
"type": "string",
"value": "={{ $('n8n Form Trigger').item.json['Notion block URL'].substr(0, $('n8n Form Trigger').item.json['Notion block URL'].indexOf('?')) || $('n8n Form Trigger').item.json['Notion block URL'] }}"
},
{
"id": "3df2b2e6-38ca-4fb6-b00c-e3a6ceb3f9b3",
"name": "root_content",
"type": "object",
"value": "={{ $('Set assignee and title').item.json[$('Set assignee and title').item.json.type] }}"
},
{
"id": "41a18b43-49fd-45a5-850d-55b9f08f9b93",
"name": "root_id",
"type": "string",
"value": "={{ $('Set assignee and title').item.json.id }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.3
},
{
"id": "bd107990-b009-4b81-8fee-a80c9ab09b4c",
"name": "设置团队 ID",
"type": "n8n-nodes-base.set",
"position": [
-740,
1760
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b22a4a67-67b5-415a-ab38-4d7f781e8b7e",
"name": "team_id",
"type": "string",
"value": "={{ $json.data.teams.nodes[0].id }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "010105b6-38a2-4859-9e5d-b9624addeacc",
"name": "向 Notion 块添加链接",
"type": "n8n-nodes-base.httpRequest",
"position": [
-560,
2620
],
"parameters": {
"url": "=https://api.notion.com/v1/blocks/{{ $('Loop Over Items').item.json.id }}",
"method": "PATCH",
"options": {},
"jsonBody": "={\n \"to_do\":\n {{ JSON.stringify($('Set assignee and title').item.json.new_content).replace('$url', $json.data.issue.url) }}\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Notion-Version",
"value": "2022-06-28"
}
]
},
"nodeCredentialType": "notionApi"
},
"credentials": {
"notionApi": {
"id": "80",
"name": "Notion david-internal"
}
},
"typeVersion": 4.1
},
{
"id": "bb88f7df-deef-4c9d-b8af-3e59bf0e0b7d",
"name": "获取问题 URL",
"type": "n8n-nodes-base.graphql",
"position": [
-780,
2620
],
"parameters": {
"query": "=query IssueDetails {\n issue(id: \"{{ $json.id }}\") {\n url\n }\n}",
"endpoint": "https://api.linear.app/graphql",
"requestMethod": "GET",
"authentication": "headerAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "zYILrk4RKFqdP66s",
"name": "[Omar] Notion credentials for GraphQL API"
}
},
"executeOnce": true,
"typeVersion": 1,
"continueOnFail": true
},
{
"id": "00736596-08ee-4ff6-b907-bd454dd406d9",
"name": "缩短标题",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
-1000,
2080
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4",
"cachedResultName": "GPT-4"
},
"options": {},
"messages": {
"values": [
{
"content": "=Make the following text more concise, so that it's max 150 chars long. If it's already less than 70 chars long, just return the original text. Do not return anything else other than the text.\n\nTEXT:\n{{ $json.title }}"
}
]
}
},
"credentials": {
"openAiApi": {
"id": "VQtv7frm7eLiEDnd",
"name": "OpenAi account 7"
}
},
"typeVersion": 1
},
{
"id": "729928a2-8a5e-4b25-82be-ba1916b9953f",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
2020
],
"parameters": {
"color": 7,
"width": 877.8549621677266,
"height": 214.7985362687051,
"content": "### 确定问题分配人和标题(必要时缩短)"
},
"typeVersion": 1
},
{
"id": "86d3b72f-f235-4b37-877f-c8ab1f39ba30",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
2280
],
"parameters": {
"color": 7,
"width": 877.8549621677266,
"height": 216.9904777194533,
"content": "### 编写问题描述"
},
"typeVersion": 1
},
{
"id": "01c06352-76fa-4d63-b37b-2a0239d81302",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
2560
],
"parameters": {
"color": 7,
"width": 877.8549621677266,
"height": 216.9904777194533,
"content": "### 创建问题并在 Notion 中添加链接"
},
"typeVersion": 1
},
{
"id": "1f152f08-0c5a-4806-bd85-7e9a5e386fe4",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
1500
],
"parameters": {
"color": 7,
"width": 1164.99929221574,
"height": 442.760447146518,
"content": "### 从 Notion 获取要创建的问题(并加载 Linear 团队详情)"
},
"typeVersion": 1
},
{
"id": "839d1815-419d-4acb-8668-94f1cbe45f1f",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1780,
1700
],
"parameters": {
"height": 278.9250421966361,
"content": "# 试用我"
},
"typeVersion": 1
},
{
"id": "f9bc6e67-a9f0-4c9b-a9b3-fdea1fd9de3e",
"name": "仅未导入、未选中的待办事项块",
"type": "n8n-nodes-base.filter",
"position": [
-220,
1760
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d7e85c09-8548-4fc8-a8a9-636e4529e9d9",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.type }}",
"rightValue": "to_do"
},
{
"id": "13fb565d-8951-4c89-9684-85c357459794",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !$json.to_do.text.reduce((s, o) => s + o.plain_text, \"\").startsWith('[In Linear]') }}",
"rightValue": ""
},
{
"id": "0a9c8e94-11ec-4317-8de5-f22862555b78",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.to_do.checked }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "186a4272-4550-441e-9ef2-66de2dac5b8a",
"name": "n8n Form Trigger",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1460,
1760
],
"webhookId": "5a631d63-f899-4967-acad-69924674e96a",
"parameters": {
"path": "5a631d63-f899-4967-acad-69924674e96a",
"formTitle": "Import Linear issues from Notion",
"formFields": {
"values": [
{
"fieldLabel": "Notion page URL",
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "Linear team name",
"fieldOptions": {
"values": [
{
"option": "AI"
},
{
"option": "Adore"
},
{
"option": "Payday"
},
{
"option": "NODES"
}
]
},
"requiredField": true
}
]
},
"responseMode": "responseNode",
"formDescription": "More information on Notion formatting required here: https://www.notion.so/n8n/8848dd09892341969faedd1313eea586"
},
"typeVersion": 2
},
{
"id": "0fed5dbe-54e9-4bc3-8ab3-6175347ecce7",
"name": "获取问题",
"type": "n8n-nodes-base.notion",
"onError": "continueErrorOutput",
"position": [
-500,
1760
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "url",
"value": "={{ $('n8n Form Trigger').item.json['Notion page URL'] }}"
},
"resource": "block",
"operation": "getAll",
"returnAll": true,
"simplifyOutput": false
},
"credentials": {
"notionApi": {
"id": "80",
"name": "Notion david-internal"
}
},
"typeVersion": 2.1
},
{
"id": "7cc756ad-55f3-4596-989a-df90d4c829c7",
"name": "将内容转换为 Markdown",
"type": "n8n-nodes-base.code",
"position": [
-780,
2340
],
"parameters": {
"jsCode": "function extractMarkdown(obj) {\n console.log('obj', obj.text)\n return obj.text.reduce((s, o) => {\n if(o.text?.link) {\n return s + '[' + o.text.content + '](' + o.text.link?.url + ')'\n }\n return s + o.text.content\n }, \"\")\n}\n\n\nconst indent = \" \"; // Four spaces\nlet parent_ids = [$input.all()[0].json.root_id]\n\nfor(item of $input.all()){\n\n // Generate the markdown\n\n if(item.json.type) {\n \n const type = item.json.type\n \n if(type == 'bulleted_list_item' || type == 'toggle') {\n item.json.markdown = '* ' + extractMarkdown(item.json[type])\n } else if(type == 'numbered_list_item') {\n item.json.markdown = '1. ' + extractMarkdown(item.json[type])\n } else if(type == 'to_do') {\n item.json.markdown = '+ [ ] ' + extractMarkdown(item.json[type])\n } else if(type == 'image') {\n item.json.markdown = ''\n } else if(type == 'video') {\n item.json.markdown = '[🎬 Video]('+$input.all()[0].json.page_url + '?pvs=4#' + item.json.id.replaceAll('-', '') +')'\n } else {\n item.json.markdown = extractMarkdown(item.json[type])\n }\n \n // Figure out how much to indent it\n // If parent ID is in list, remove everything after that ID\n // If parent ID is not in list, add it\n // If parent is the same, do nothing\n const parent_id_index = parent_ids.indexOf(item.json.parent_id);\n \n // Check if the value is found\n if (parent_id_index !== -1) {\n // Remove all elements after the first occurrence\n parent_ids.splice(parent_id_index + 1);\n } else {\n parent_ids.push(item.json.parent_id)\n }\n \n // Indent the markdown\n //if (type != \"image\") {\n item.json.markdown = indent.repeat(parent_ids.length - 1) + item.json.markdown\n //}\n }\n}\n\n// On returning, add in the root block content at the beginning\nreturn [\n ...[\n {\n \"json\": {\n \"markdown\":\nextractMarkdown($input.all()[0].json.root_content)\n },\n \"pairedItem\": 0\n }\n ],\n ...$input.all()\n]"
},
"typeVersion": 2
},
{
"id": "2bbd43a2-aebe-4ee5-8682-d76791214168",
"name": "错误响应",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-220,
1580
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"formSubmittedText\": \"Couldn't fetch page content from Notion. Is it shared with your Notion integration?\"\n}"
},
"typeVersion": 1
},
{
"id": "c39912c4-d33f-4d79-9d5c-e71bd0f2517d",
"name": "错误响应1",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-740,
1580
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\n \"formSubmittedText\": \"Couldn't find the team called '\" + {{ $('n8n Form Trigger').item.json['Linear team name'] }} + \"'\"\n} "
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Aggregate": {
"main": [
[
{
"node": "Prepare issue data",
"type": "main",
"index": 0
}
]
]
},
"Get issues": {
"main": [
[
{
"node": "Unimported, unchecked to_do blocks only",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond with error",
"type": "main",
"index": 0
}
]
]
},
"Set team ID": {
"main": [
[
{
"node": "Get issues",
"type": "main",
"index": 0
}
]
]
},
"Set page URL": {
"main": [
[
{
"node": "Convert contents to Markdown",
"type": "main",
"index": 0
}
]
]
},
"Get issue URL": {
"main": [
[
{
"node": "Add link to Notion block",
"type": "main",
"index": 0
}
]
]
},
"Shorten title": {
"main": [
[
{
"node": "Get issue contents",
"type": "main",
"index": 0
}
]
]
},
"Team missing?": {
"main": [
[
{
"node": "Respond with error1",
"type": "main",
"index": 0
}
],
[
{
"node": "Set team ID",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
null,
[
{
"node": "Set assignee and title",
"type": "main",
"index": 0
}
]
]
},
"n8n Form Trigger": {
"main": [
[
{
"node": "Fetch Linear team details",
"type": "main",
"index": 0
}
]
]
},
"Get issue contents": {
"main": [
[
{
"node": "Set page URL",
"type": "main",
"index": 0
}
]
]
},
"Prepare issue data": {
"main": [
[
{
"node": "Create linear issue",
"type": "main",
"index": 0
}
]
]
},
"Create linear issue": {
"main": [
[
{
"node": "Get issue URL",
"type": "main",
"index": 0
}
]
]
},
"Set assignee and title": {
"main": [
[
{
"node": "Shorten title",
"type": "main",
"index": 0
}
]
]
},
"Add link to Notion block": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Fetch Linear team details": {
"main": [
[
{
"node": "Team missing?",
"type": "main",
"index": 0
}
]
]
},
"Convert contents to Markdown": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Unimported, unchecked to_do blocks only": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 工程, 产品
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
实时Notion Todoist双向同步模板
使用Redis的Notion Todoist实时双向同步
If
Set
Code
+26
246 节点Mario
销售
API架构提取器
API架构提取器
If
Set
Code
+22
88 节点Polina Medvedieva
工程
在可视化参考库中探索n8n节点
在可视化参考库中探索n8n节点
If
Ftp
Set
+93
113 节点I versus AI
其他
WordPress 内容生成器 v3
WordPress 内容生成器 v3
If
Set
Code
+21
102 节点Alex Kim
人工智能
深度研究智能体
深度研究智能体 - 自动化研究与 Notion 报告生成器
Set
Code
Filter
+17
43 节点Aziz B
市场调研
评估指标示例:字符串相似度
评估指标示例:字符串相似度
Set
Code
Webhook
+6
12 节点David Roberts
工程