This workflow corresponds to n8n.io template #11194 — we link there as the canonical source.
This workflow follows the Agent → Chat Trigger 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 →
{
"id": "cL8wgsK6Nj241iSS",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Prompt Hub",
"tags": [],
"nodes": [
{
"id": "31d202b9-3206-4fd4-869a-47b408d8d553",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
-880,
-64
],
"parameters": {
"public": true,
"options": {},
"initialMessages": "Hi there! \ud83d\udc4b\nMy name is JARVIS. How can I assist you today?"
},
"typeVersion": 1.1
},
{
"id": "09af7356-eaaa-4677-bc78-c84732b8b850",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-672,
-64
],
"parameters": {
"options": {
"systemMessage": "You are an assistant connected to a Prompt Hub.\n\nFor every user message, follow this workflow:\n\n1. **Always first** call the tool `Search a Prompt` with the entire user message as `query`.\n2. If the tool returns a matching prompt (`notFound = false`):\n - Do NOT answer the user yet.\n - Show the found prompt to the user.\n - Ask: \n **\"I found a saved prompt that matches your request. Do you want to use it?\"**\n - Then wait for the user's confirmation.\n3. If the tool returns no prompt (`notFound = true`):\n - Continue normally and answer the user's request with the model.\n - Do not call the tool again until the next user message.\n\nNever skip the prompt search step. The first step for **every** user message is always a call to `Search a Prompt`."
}
},
"typeVersion": 2.1
},
{
"id": "4bb56284-f730-4775-9df1-814d01ec5cb6",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-720,
176
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "5ef42413-4cb1-4d93-961d-06142a1544e2",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-544,
160
],
"parameters": {
"contextWindowLength": 10
},
"typeVersion": 1.3
},
{
"id": "77e12cfa-11ea-43c4-bf1f-9fb38e2ae9b3",
"name": "Embeddings - Sync All",
"type": "n8n-nodes-base.webhook",
"position": [
-768,
-528
],
"parameters": {
"path": "/embeddings/sync-all",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "3cafdcd4-dd94-461b-84ef-6788b274c668",
"name": "Get All Prompts",
"type": "n8n-nodes-base.notion",
"position": [
-560,
-528
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": "2b3041f0-262c-809f-8e66-e54cbcca02f2",
"cachedResultUrl": "https://www.notion.so/2b3041f0262c809f8e66e54cbcca02f2",
"cachedResultName": "Prompt Hub"
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "fa62aebc-4476-46a1-9c7a-4c4d44750b89",
"name": "Get Embeddings",
"type": "n8n-nodes-base.httpRequest",
"position": [
96,
-544
],
"parameters": {
"url": "https://router.huggingface.co/hf-inference/models/intfloat/multilingual-e5-small/pipeline/feature-extraction",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
},
"sendBody": true,
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "inputs",
"value": "={{ $json.prompt }}"
}
]
},
"nodeCredentialType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"executeOnce": false,
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "7f020592-eba0-4107-a27b-8830e3562105",
"name": "Prepare Update Body",
"type": "n8n-nodes-base.code",
"position": [
304,
-544
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const embeddingString = $json.data;\nconst maxLen = 1900; // safety margin below 2000\nconst richText = [];\n\nfor (let i = 0; i < embeddingString.length; i += maxLen) {\n richText.push({\n text: {\n content: embeddingString.slice(i, i + maxLen),\n },\n });\n }\n\nreturn {\n json: {\n pageId: $('Generate Checksum').item.json.pageId,\n \"pageUpdateBody\": {\n \"properties\": {\n \"Embeddings\": {\n \"rich_text\": richText\n },\n \"Checksum\": {\n \"rich_text\": [\n {\n \"text\": {\n \"content\": $('Generate Checksum').item.json.newChecksum\n }\n }\n ]\n }\n }\n }\n },\n};"
},
"typeVersion": 2
},
{
"id": "6d775127-8212-4079-be91-60d0d0b6a0f9",
"name": "On Page Update",
"type": "n8n-nodes-base.notionTrigger",
"position": [
-656,
-784
],
"parameters": {
"event": "pagedUpdatedInDatabase",
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"databaseId": {
"__rl": true,
"mode": "list",
"value": "2b3041f0-262c-809f-8e66-e54cbcca02f2",
"cachedResultUrl": "https://www.notion.so/2b3041f0262c809f8e66e54cbcca02f2",
"cachedResultName": "Prompt Hub"
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "7fa97628-2853-4bac-b193-51819c0e97db",
"name": "On Page Create",
"type": "n8n-nodes-base.notionTrigger",
"position": [
-656,
-944
],
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"databaseId": {
"__rl": true,
"mode": "list",
"value": "2b3041f0-262c-809f-8e66-e54cbcca02f2",
"cachedResultUrl": "https://www.notion.so/2b3041f0262c809f8e66e54cbcca02f2",
"cachedResultName": "Prompt Hub"
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "9fe7af39-24e0-46e2-8e06-32d6ec765136",
"name": "Get Question Embeddings",
"type": "n8n-nodes-base.httpRequest",
"position": [
224,
-80
],
"parameters": {
"url": "https://router.huggingface.co/hf-inference/models/intfloat/multilingual-e5-small/pipeline/feature-extraction",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
},
"sendBody": true,
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "inputs",
"value": "={{ $json.query }}"
}
]
},
"nodeCredentialType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"executeOnce": false,
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "e16a874f-99a8-4e5a-b3a9-3b806bfda39e",
"name": "Get All Prompts for search",
"type": "n8n-nodes-base.notion",
"position": [
432,
-80
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": "2b3041f0-262c-809f-8e66-e54cbcca02f2",
"cachedResultUrl": "https://www.notion.so/2b3041f0262c809f8e66e54cbcca02f2",
"cachedResultName": "Prompt Hub"
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "f680bab1-c3d3-42c4-8b9b-8ef29b2d6491",
"name": "Find a prompt",
"type": "n8n-nodes-base.code",
"position": [
640,
-80
],
"parameters": {
"jsCode": "// Get query embedding from previous node (single item)\nconst queryEmbedding = JSON.parse($('Get Question Embeddings').first().json.data);\n\nif (!Array.isArray(queryEmbedding) || queryEmbedding.length === 0) {\n throw new Error('queryEmbedding is missing or empty');\n}\n\n// Cosine similarity\nfunction cosineSimilarity(a, b) {\n let dot = 0;\n let na = 0;\n let nb = 0;\n\n const len = Math.min(a.length, b.length);\n for (let i = 0; i < len; i++) {\n dot += a[i] * b[i];\n na += a[i] * a[i];\n nb += b[i] * b[i];\n }\n\n if (na === 0 || nb === 0) return 0;\n return dot / (Math.sqrt(na) * Math.sqrt(nb));\n}\n\nlet best = null;\n\n// Iterate over all prompts (Notion pages)\nfor (const item of $('Get All Prompts for search').all()) {\n const page = item.json;\n\n // 1) Read prompt text\n const promptText = (page.property_prompt);\n\n if (!promptText) {\n continue;\n }\n\n // 2) Rebuild embedding JSON string from rich_text chunks\n const embeddingChunks = JSON.stringify(page.property_embeddings);\n const embeddingString = page.property_embeddings;\n\n if (!embeddingString) {\n continue;\n }\n\n // 3) Parse embedding\n let embedding;\n try {\n embedding = JSON.parse(embeddingString);\n } catch (e) {\n continue; // skip broken embeddings\n }\n\n if (!Array.isArray(embedding) || embedding.length === 0) {\n continue;\n }\n\n // 4) Similarity\n const score = cosineSimilarity(queryEmbedding, embedding);\n\n if (!best || score > best.score) {\n best = {\n pageId: page.id,\n promptText,\n score,\n };\n }\n}\n\n// Threshold: if similarity is too low, treat as \"not found\"\nconst MIN_SCORE = 0.4; // tweak if needed\n\nif (!best || best.score < MIN_SCORE) {\n return [{\n json: {\n prompt: null,\n notFound: true,\n score: best ? best.score : null,\n },\n }];\n}\n\nreturn [{\n json: {\n prompt: best.promptText,\n notFound: false,\n score: best.score,\n pageId: best.pageId, // optional, might be useful\n },\n}];\n"
},
"typeVersion": 2
},
{
"id": "3b50f4e8-00f7-42d1-a0fd-f76899fbb79f",
"name": "Search a Prompt",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
-336,
176
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "list",
"value": "cL8wgsK6Nj241iSS",
"cachedResultName": "Prompt Hub \u2013 Search"
},
"description": "Find the most relevant saved prompt for the user\u2019s message.\n\nUse this tool **for every user message**, even if the user does not explicitly ask to search for prompts.\n\nInput: a natural language query (the user message).\n\nOutput:\n- the best matching saved prompt,\n- a similarity score,\n- and a `notFound` flag.\n\nAlways call this tool first before generating any answer.\n",
"workflowInputs": {
"value": {
"query": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('query', `User\u2019s request for which to find a saved prompt.`, 'string') }}"
},
"schema": [
{
"id": "query",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "query",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"query"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 2.2
},
{
"id": "c8f713ce-bbfe-44c1-937d-a1651ac46a9d",
"name": "Generate Checksum",
"type": "n8n-nodes-base.code",
"position": [
-352,
-528
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Pure JavaScript SHA-256 implementation (works in n8n sandbox)\nfunction sha256(ascii) {\n function rightRotate(value, amount) {\n return (value >>> amount) | (value << (32 - amount));\n }\n\n const mathPow = Math.pow;\n const maxWord = mathPow(2, 32);\n const lengthProperty = 'length';\n const words = [];\n const asciiBitLength = ascii[lengthProperty] * 8;\n\n let hash = sha256.h = sha256.h || [];\n let k = sha256.k = sha256.k || [];\n let primeCounter = k[lengthProperty];\n\n const isPrime = num => {\n for (let i = 2, sqrt = Math.sqrt(num); i <= sqrt; i++) {\n if (num % i === 0) return false;\n }\n return true;\n };\n\n if (!primeCounter) {\n let candidate = 2;\n while (primeCounter < 64) {\n if (isPrime(candidate)) {\n hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;\n k[primeCounter++] = (mathPow(candidate, 1/3) * maxWord) | 0;\n }\n candidate++;\n }\n }\n\n ascii += '\\x80'; // append \u0187' bit\n while (ascii[lengthProperty] % 64 - 56) ascii += '\\x00';\n for (let i = 0; i < ascii[lengthProperty]; i++) {\n const j = ascii.charCodeAt(i);\n words[i >> 2] |= j << ((3 - i) % 4) * 8;\n }\n words[words[lengthProperty]] = (asciiBitLength / maxWord) | 0;\n words[words[lengthProperty]] = asciiBitLength;\n\n for (let j = 0; j < words[lengthProperty];) {\n const w = words.slice(j, j += 16);\n const oldHash = hash.slice(0);\n\n for (let i = 16; i < 64; i++) {\n const a = w[i - 15];\n const b = w[i - 2];\n w[i] = (\n ((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14))\n + ((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13))\n + w[i - 7] + w[i - 16]\n ) | 0;\n }\n\n for (let i = 0; i < 64; i++) {\n const temp = (\n hash[7]\n + ((hash[4] >>> 6) ^ (hash[4] >>> 11) ^ (hash[4] >>> 25) ^ (hash[4] << 26) ^ (hash[4] << 21) ^ (hash[4] << 7))\n + ((hash[4] & hash[5]) ^ (~hash[4] & hash[6]))\n + k[i] + w[i]\n ) | 0;\n\n const temp2 = (\n ((hash[0] >>> 2) ^ (hash[0] >>> 13) ^ (hash[0] >>> 22) ^ (hash[0] << 30) ^ (hash[0] << 19) ^ (hash[0] << 10))\n + ((hash[0] & hash[1]) ^ (hash[0] & hash[2]) ^ (hash[1] & hash[2]))\n ) | 0;\n\n hash = [\n (temp + temp2) | 0,\n hash[0],\n hash[1],\n hash[2],\n (hash[3] + temp) | 0,\n hash[4],\n hash[5],\n hash[6]\n ];\n }\n\n for (let i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;\n }\n\n return hash.map(h =>\n ('00000000' + (h >>> 0).toString(16)).slice(-8)\n ).join('');\n}\n\nconst prompt = $json.property_prompt || $json.Prompt;\nconst checksum = $json.property_checksum || $json.Checksum;\n\nconst newChecksum = sha256(prompt);\n\n// Main iteration loop for n8n items\nreturn {\n json: {\n pageId: $json.id,\n prompt: prompt,\n oldChecksum: checksum,\n newChecksum: newChecksum,\n needsUpdate: !checksum\n || checksum !== newChecksum\n }\n};\n"
},
"typeVersion": 2,
"alwaysOutputData": false
},
{
"id": "75f0ccd1-800c-4709-9adf-6e16e02ecf5f",
"name": "If Embeddings Update Needed",
"type": "n8n-nodes-base.if",
"position": [
-144,
-528
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0b3d16ed-f2a0-407a-bfcf-9ee1b343df49",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.needsUpdate }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "effc4289-47f9-47b2-b3a3-480d7ca41dc5",
"name": "Save Embeddings + Checksum",
"type": "n8n-nodes-base.httpRequest",
"position": [
512,
-544
],
"parameters": {
"url": "=https://api.notion.com/v1/pages/{{ $json.pageId }}",
"method": "PATCH",
"options": {},
"jsonBody": "={{ $json.pageUpdateBody }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "notionApi"
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "ce3bee4e-de94-474c-a24e-fe667df78df3",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-944,
-256
],
"parameters": {
"color": 7,
"width": 752,
"height": 576,
"content": "## Chat Interface\n\nChatGPT interface that searches for prompts on each message. If found, suggests the prompt; otherwise responds normally."
},
"typeVersion": 1
},
{
"id": "f9287e40-ea76-49ae-a317-d54cf25b2591",
"name": "Search Entrypoint",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
0,
-80
],
"parameters": {
"workflowInputs": {
"values": [
{
"name": "query"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "41f956b6-0687-4010-ad71-9c63d8f9a2fd",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-128,
-240
],
"parameters": {
"color": 7,
"width": 1008,
"height": 368,
"content": "## Prompt Finder\n\nSearches Notion prompts using semantic similarity. Compares user message embeddings with saved prompt embeddings."
},
"typeVersion": 1
},
{
"id": "83d02c0f-4100-4412-be5e-ea194554bd3b",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1744,
-1104
],
"parameters": {
"width": 736,
"height": 672,
"content": "# GPT-Integrated Prompt Hub\n\n## How it works\n\nThis workflow automatically searches your saved prompts in Notion when you chat with ChatGPT. When you send any message, the AI agent first searches your Notion database using semantic similarity. It converts both your message and saved prompts into numerical vectors (embeddings) using HuggingFace, then compares them to find the most similar match. If a matching prompt is found, it suggests it to you. If not, it responds normally.\n\nThe workflow has three main parts:\n1. **Embeddings Generator** - Automatically creates embeddings when you add/update prompts in Notion, or regenerates all embeddings via webhook trigger\n2. **Chat Interface** - The ChatGPT interface that triggers prompt search on each message\n3. **Prompt Finder** - Compares your message with saved prompts to find the best match\n\n## Setup steps\n\n1. Import this workflow and configure credentials: OpenAI, HuggingFace, and Notion\n2. Create a Notion database with columns: Prompt (Text), Embeddings (Text), Checksum (Text)\n3. Set your Notion Database ID in these nodes: \"Get All Prompts\", \"On Page Update\", \"On Page Create\", \"Get All Prompts for search\"\n4. Activate the workflow, open the URL from the \"When chat message received\" node, and start chatting"
},
"typeVersion": 1
},
{
"id": "0d39103e-eb6a-4a33-aca6-9c7f6351bd6f",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-944,
-1104
],
"parameters": {
"color": 7,
"width": 1680,
"height": 752,
"content": "## Embeddings Generator\n\nGenerates embeddings for new/updated prompts. Triggers: Notion page create/update or manual sync webhook to update all prompts' embeddings."
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "670bcdab-88e4-4b79-8141-cba141e9388b",
"connections": {
"Simple Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Get Embeddings": {
"main": [
[
{
"node": "Prepare Update Body",
"type": "main",
"index": 0
}
]
]
},
"On Page Create": {
"main": [
[
{
"node": "Generate Checksum",
"type": "main",
"index": 0
}
]
]
},
"On Page Update": {
"main": [
[
{
"node": "Generate Checksum",
"type": "main",
"index": 0
}
]
]
},
"Get All Prompts": {
"main": [
[
{
"node": "Generate Checksum",
"type": "main",
"index": 0
}
]
]
},
"Search a Prompt": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Generate Checksum": {
"main": [
[
{
"node": "If Embeddings Update Needed",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Search Entrypoint": {
"main": [
[
{
"node": "Get Question Embeddings",
"type": "main",
"index": 0
}
]
]
},
"Prepare Update Body": {
"main": [
[
{
"node": "Save Embeddings + Checksum",
"type": "main",
"index": 0
}
]
]
},
"Embeddings - Sync All": {
"main": [
[
{
"node": "Get All Prompts",
"type": "main",
"index": 0
}
]
]
},
"Get Question Embeddings": {
"main": [
[
{
"node": "Get All Prompts for search",
"type": "main",
"index": 0
}
]
]
},
"Get All Prompts for search": {
"main": [
[
{
"node": "Find a prompt",
"type": "main",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"If Embeddings Update Needed": {
"main": [
[
{
"node": "Get Embeddings",
"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.
httpBearerAuthnotionApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Build your own AI Prompt Hub inside n8n. This template lets ChatGPT automatically search your saved prompts in Notion using semantic embeddings from HuggingFace. Each time a user sends a message, the workflow finds the most relevant prompt based on meaning - not keywords.
Source: https://n8n.io/workflows/11194/ — 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.
This template attempts to create an AI-powered content assistant for WordPress sites using Mistral AI, enabling article recommendations, content summarization, and contextual Q&A capabilities.
by Varritech Technologies
Airtable AI Agent. Uses lmChatOpenAi, agent, toolWorkflow, toolCode. Chat trigger; 42 nodes.
Ai Agent To Chat With Airtable And Analyze Data. Uses lmChatOpenAi, agent, stickyNote, memoryBufferWindow. Chat trigger; 41 nodes.
I prepared a detailed guide that shows the entire process of building an AI agent that integrates with Airtable data in n8n. This template covers everything from data preparation to advanced configura