AutomationFlowsAI & RAG › Sync Affine Content to Vector Store

Sync Affine Content to Vector Store

Original n8n title: Affine Content Sync to Vector Store

Affine Content Sync to Vector Store. Uses httpRequest, postgres, textSplitterRecursiveCharacterTextSplitter, documentDefaultDataLoader. Scheduled trigger; 19 nodes.

Cron / scheduled trigger★★★★☆ complexityAI-powered19 nodesHTTP RequestPostgresText Splitter Recursive Character Text SplitterDocument Default Data LoaderOllama EmbeddingsQdrant Vector Store
AI & RAG Trigger: Cron / scheduled Nodes: 19 Complexity: ★★★★☆ AI nodes: yes Added:
Sync Affine Content to Vector Store — n8n workflow card showing HTTP Request, Postgres, Text Splitter Recursive Character Text Splitter integration

This workflow follows the Documentdefaultdataloader → HTTP Request 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 →

Download .json
{
  "name": "Affine Content Sync to Vector Store",
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "mode": "polling",
        "options": {}
      },
      "id": "affine-polling-trigger",
      "name": "Affine Polling Trigger",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "url": "http://affine:3010/graphql",
        "options": {
          "timeout": 30000,
          "retry": {
            "enabled": true,
            "maxAttempts": 3
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "Bearer {{ $vars.affine_api_token }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": {
          "query": "query GetWorkspaceDocuments($workspaceId: String!) { workspace(id: $workspaceId) { id name docs { id title content updatedAt createdAt } } }",
          "variables": {
            "workspaceId": "{{ $vars.affine_workspace_id }}"
          }
        }
      },
      "id": "fetch-affine-documents",
      "name": "Fetch Affine Documents",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        450,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT document_id, content_hash FROM knowledge_sync_log WHERE source_system = 'affine' AND status = 'synced'",
        "options": {}
      },
      "id": "get-existing-hashes",
      "name": "Get Existing Hashes",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        450,
        500
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "existing-hashes-map",
              "name": "existingHashes",
              "value": "={{ $('Get Existing Hashes').all().reduce((acc, item) => { acc[item.document_id] = item.content_hash; return acc; }, {}) }}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "id": "prepare-hash-lookup",
      "name": "Prepare Hash Lookup",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        700,
        400
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "documents-array",
              "name": "documents",
              "value": "={{ $json.data?.workspace?.docs || [] }}",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "id": "extract-documents",
      "name": "Extract Documents",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        700,
        300
      ]
    },
    {
      "parameters": {
        "fieldToSplitOut": "documents",
        "options": {}
      },
      "id": "split-documents",
      "name": "Split Into Documents",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        950,
        300
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "content-processing",
              "name": "content",
              "value": "={{ $json.title ? $json.title + '\\n\\n' + ($json.content || 'No content available') : ($json.content || 'No content available') }}",
              "type": "string"
            },
            {
              "id": "content-hash",
              "name": "contentHash",
              "value": "={{ $crypto.createHash('md5').update($json.content || '').digest('hex') }}",
              "type": "string"
            },
            {
              "id": "document-id-affine",
              "name": "document_id",
              "value": "={{ 'affine_' + $json.id }}",
              "type": "string"
            },
            {
              "id": "metadata-affine",
              "name": "metadata",
              "value": "={{ { source: 'affine', type: 'document', doc_id: $json.id, workspace_id: $vars.affine_workspace_id, title: $json.title, created_at: $json.createdAt, updated_at: $json.updatedAt } }}",
              "type": "object"
            },
            {
              "id": "needs-update",
              "name": "needsUpdate",
              "value": "={{ $('Prepare Hash Lookup').item.json.existingHashes[$json.document_id] !== $json.contentHash }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "process-affine-document",
      "name": "Process Affine Document",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1200,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "needs-update-condition",
              "leftValue": "={{ $json.needsUpdate }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-if-update-needed",
      "name": "Check If Update Needed",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1450,
        300
      ]
    },
    {
      "parameters": {
        "code": {
          "execute": {
            "code": "const { QdrantVectorStore } = require(\"@langchain/qdrant\");\nconst { OllamaEmbeddings } = require(\"@langchain/community/embeddings/ollama\");\n\nconst embeddings = new OllamaEmbeddings({\n  model: \"nomic-embed-text\",\n  baseUrl: \"http://ollama:11434\"\n});\n\nconst vectorStore = await QdrantVectorStore.fromExistingCollection(\n  embeddings,\n  {\n    url: \"http://qdrant:6333\",\n    collectionName: \"knowledge_base\",\n  }\n);\n\nconst documentIdToDelete = this.getInputData()[0].json.document_id;\n\nconst filter = {\n  must: [\n    {\n      key: \"metadata.document_id\",\n      match: {\n        value: documentIdToDelete,\n      },\n    },\n  ],\n};\n\n// Delete existing vectors for this document\ntry {\n  await vectorStore.client.delete(\"knowledge_base\", {\n    filter\n  });\n  console.log(`Cleared vectors for document: ${documentIdToDelete}`);\n} catch (error) {\n  console.log(`No existing vectors found for document: ${documentIdToDelete}`);\n}\n\nreturn [{ json: { document_id: documentIdToDelete, status: \"cleared\" } }];"
          }
        },
        "inputs": {
          "input": [
            {
              "type": "main",
              "required": true
            }
          ]
        },
        "outputs": {
          "output": [
            {
              "type": "main"
            }
          ]
        }
      },
      "id": "clear-existing-affine-vectors",
      "name": "Clear Existing Affine Vectors",
      "type": "@n8n/n8n-nodes-langchain.code",
      "typeVersion": 1,
      "position": [
        1700,
        200
      ]
    },
    {
      "parameters": {
        "chunkSize": 500,
        "chunkOverlap": 50,
        "options": {}
      },
      "id": "affine-text-splitter",
      "name": "Affine Text Splitter",
      "type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
      "typeVersion": 1,
      "position": [
        1950,
        400
      ]
    },
    {
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "source",
                "value": "={{ $('Process Affine Document').item.json.metadata.source }}"
              },
              {
                "name": "type",
                "value": "={{ $('Process Affine Document').item.json.metadata.type }}"
              },
              {
                "name": "doc_id",
                "value": "={{ $('Process Affine Document').item.json.metadata.doc_id }}"
              },
              {
                "name": "document_id",
                "value": "={{ $('Process Affine Document').item.json.document_id }}"
              },
              {
                "name": "workspace_id",
                "value": "={{ $('Process Affine Document').item.json.metadata.workspace_id }}"
              },
              {
                "name": "title",
                "value": "={{ $('Process Affine Document').item.json.metadata.title }}"
              },
              {
                "name": "updated_at",
                "value": "={{ $('Process Affine Document').item.json.metadata.updated_at }}"
              }
            ]
          }
        }
      },
      "id": "affine-document-loader",
      "name": "Affine Document Loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "typeVersion": 1,
      "position": [
        1950,
        200
      ]
    },
    {
      "parameters": {
        "model": "nomic-embed-text:latest"
      },
      "id": "affine-embeddings-ollama",
      "name": "Affine Embeddings Ollama",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOllama",
      "typeVersion": 1,
      "position": [
        2200,
        200
      ],
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "insert",
        "qdrantCollection": {
          "__rl": true,
          "value": "knowledge_base",
          "mode": "list",
          "cachedResultName": "knowledge_base"
        },
        "options": {}
      },
      "id": "affine-qdrant-vector-store",
      "name": "Affine Qdrant Vector Store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
      "typeVersion": 1,
      "position": [
        2450,
        200
      ],
      "credentials": {
        "qdrantApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "upsert",
        "table": {
          "__rl": true,
          "value": "knowledge_sync_log",
          "mode": "list"
        },
        "data": {
          "insert": [
            {
              "column": "document_id",
              "value": "={{ $('Process Affine Document').item.json.document_id }}"
            },
            {
              "column": "source_system",
              "value": "affine"
            },
            {
              "column": "source_id",
              "value": "={{ $('Process Affine Document').item.json.metadata.doc_id }}"
            },
            {
              "column": "content_hash",
              "value": "={{ $('Process Affine Document').item.json.contentHash }}"
            },
            {
              "column": "metadata",
              "value": "={{ $('Process Affine Document').item.json.metadata }}"
            },
            {
              "column": "sync_timestamp",
              "value": "={{ $now }}"
            },
            {
              "column": "status",
              "value": "synced"
            }
          ]
        },
        "options": {}
      },
      "id": "log-affine-sync-status",
      "name": "Log Affine Sync Status",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2700,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "affine-content-webhook",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "affine-webhook-trigger",
      "name": "Affine Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        200,
        500
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "webhook-doc-id",
              "name": "document_id",
              "value": "={{ 'affine_' + $json.body.docId }}",
              "type": "string"
            },
            {
              "id": "webhook-workspace-id",
              "name": "workspace_id",
              "value": "={{ $json.body.workspaceId }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "process-webhook-data",
      "name": "Process Webhook Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        450,
        600
      ]
    },
    {
      "parameters": {
        "url": "http://affine:3010/graphql",
        "options": {
          "timeout": 30000
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "Bearer {{ $vars.affine_api_token }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": {
          "query": "query GetDocument($workspaceId: String!, $docId: String!) { workspace(id: $workspaceId) { doc(id: $docId) { id title content updatedAt createdAt } } }",
          "variables": {
            "workspaceId": "={{ $json.workspace_id }}",
            "docId": "={{ $json.body.docId }}"
          }
        }
      },
      "id": "fetch-single-affine-document",
      "name": "Fetch Single Affine Document",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        700,
        600
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "respond-to-affine-webhook",
      "name": "Respond to Affine Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        950,
        600
      ]
    },
    {
      "parameters": {
        "content": "## Affine Content Sync to Vector Store\n\n**Purpose:** Automatically sync Affine workspace documents to the central knowledge vector store.\n\n**Triggers:**\n- Scheduled polling (every minute)\n- Webhook notifications for real-time updates\n\n**Process:**\n1. Fetch documents from Affine GraphQL API\n2. Check existing content hashes to detect changes\n3. Process only new or updated documents\n4. Clear existing vectors for updated content\n5. Split content into chunks and generate embeddings\n6. Store in Qdrant vector database\n7. Log sync status to PostgreSQL\n\n**Note:** Uses internal GraphQL API - requires proper authentication",
        "height": 450,
        "width": 600,
        "color": 5
      },
      "id": "affine-workflow-documentation",
      "name": "Affine Workflow Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2800,
        100
      ]
    }
  ],
  "connections": {
    "Affine Polling Trigger": {
      "main": [
        [
          {
            "node": "Fetch Affine Documents",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Existing Hashes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Affine Documents": {
      "main": [
        [
          {
            "node": "Extract Documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Existing Hashes": {
      "main": [
        [
          {
            "node": "Prepare Hash Lookup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Hash Lookup": {
      "main": [
        [
          {
            "node": "Split Into Documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Documents": {
      "main": [
        [
          {
            "node": "Split Into Documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Into Documents": {
      "main": [
        [
          {
            "node": "Process Affine Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Affine Document": {
      "main": [
        [
          {
            "node": "Check If Update Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Update Needed": {
      "main": [
        [
          {
            "node": "Clear Existing Affine Vectors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear Existing Affine Vectors": {
      "main": [
        [
          {
            "node": "Affine Document Loader",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Affine Text Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Affine Document Loader",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    },
    "Affine Document Loader": {
      "ai_document": [
        [
          {
            "node": "Affine Qdrant Vector Store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Affine Embeddings Ollama": {
      "ai_embedding": [
        [
          {
            "node": "Affine Qdrant Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Affine Qdrant Vector Store": {
      "main": [
        [
          {
            "node": "Log Affine Sync Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Affine Webhook Trigger": {
      "main": [
        [
          {
            "node": "Process Webhook Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Webhook Data": {
      "main": [
        [
          {
            "node": "Fetch Single Affine Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Single Affine Document": {
      "main": [
        [
          {
            "node": "Respond to Affine Webhook",
            "type": "main",
            "index": 0
          },
          {
            "node": "Process Affine Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "affine-sync-v1",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "AffineContentSync",
  "tags": [
    "knowledge-management",
    "affine",
    "vector-store",
    "sync"
  ]
}

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

About this workflow

Affine Content Sync to Vector Store. Uses httpRequest, postgres, textSplitterRecursiveCharacterTextSplitter, documentDefaultDataLoader. Scheduled trigger; 19 nodes.

Source: https://github.com/161sam/n8n-installer/blob/main/modularium/ai-workspace/Affine-Content-Sync.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

Google Drive Knowledge Sync. Uses googleDriveTrigger, googleDrive, textSplitterRecursiveCharacterTextSplitter, documentDefaultDataLoader. Event-driven trigger; 20 nodes.

Google Drive Trigger, Google Drive, Text Splitter Recursive Character Text Splitter +5
AI & RAG

Search Worflow Docker Complete. Uses documentDefaultDataLoader, textSplitterCharacterTextSplitter, vectorStoreSupabase, embeddingsOllama. Scheduled trigger; 71 nodes.

Document Default Data Loader, Text Splitter Character Text Splitter, Supabase Vector Store +14
AI & RAG

HeyDinastia. Uses executeCommand, httpRequest, youTube, postgres. Webhook trigger; 66 nodes.

Execute Command, HTTP Request, YouTube +15
AI & RAG

This workflow implements a self-healing Retrieval-Augmented Generation (RAG) maintenance system that automatically updates document embeddings, evaluates retrieval quality, detects embedding drift, an

HTTP Request, Postgres, OpenAI Embeddings +5
AI & RAG

This workflow automates end-to-end e-commerce order processing from intake through fulfillment by orchestrating multiple AI-powered validation stages and external system integrations. Designed for e-c

HTTP Request, Agent, OpenAI Chat +10