AutomationFlowsData & Sheets › Sync AppFlowy and Affine Knowledge Bidirectionally

Sync AppFlowy and Affine Knowledge Bidirectionally

Original n8n title: Bidirectional Knowledge Sync Between Appflowy and Affine

Bidirectional Knowledge Sync Between AppFlowy and Affine. Uses postgres, n8n-nodes-appflowy, httpRequest. Webhook trigger; 22 nodes.

Webhook trigger★★★★☆ complexity22 nodesPostgresN8N Nodes AppflowyHTTP Request
Data & Sheets Trigger: Webhook Nodes: 22 Complexity: ★★★★☆ Added:
Sync AppFlowy and Affine Knowledge Bidirectionally — n8n workflow card showing Postgres, N8N Nodes Appflowy, HTTP Request integration

This workflow follows the HTTP Request → Postgres 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": "Bidirectional Knowledge Sync Between AppFlowy and Affine",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "trigger-knowledge-sync",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "knowledge-sync-trigger",
      "name": "Knowledge Sync Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        200,
        400
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "sync-source",
              "name": "source",
              "value": "={{ $json.body.source || 'all' }}",
              "type": "string"
            },
            {
              "id": "sync-direction",
              "name": "direction",
              "value": "={{ $json.body.direction || 'bidirectional' }}",
              "type": "string"
            },
            {
              "id": "sync-filter",
              "name": "filter",
              "value": "={{ $json.body.filter || {} }}",
              "type": "object"
            },
            {
              "id": "force-sync",
              "name": "forceSync",
              "value": "={{ $json.body.forceSync || false }}",
              "type": "boolean"
            },
            {
              "id": "sync-timestamp",
              "name": "syncTimestamp",
              "value": "={{ $now }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "process-sync-request",
      "name": "Process Sync Request",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        450,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM knowledge_sync_mapping WHERE (source_system = 'appflowy' OR target_system = 'appflowy') AND status = 'active'",
        "options": {}
      },
      "id": "get-appflowy-mappings",
      "name": "Get AppFlowy Mappings",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        700,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM knowledge_sync_mapping WHERE (source_system = 'affine' OR target_system = 'affine') AND status = 'active'",
        "options": {}
      },
      "id": "get-affine-mappings",
      "name": "Get Affine Mappings",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        700,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT DISTINCT document_id, source_system, source_id, metadata, sync_timestamp FROM knowledge_sync_log WHERE (source_system = 'appflowy' OR source_system = 'affine') AND status = 'synced' ORDER BY sync_timestamp DESC",
        "options": {}
      },
      "id": "get-recent-changes",
      "name": "Get Recent Changes",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        700,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "appflowy-changes",
              "name": "appFlowyChanges",
              "value": "={{ $('Get Recent Changes').all().filter(item => item.source_system === 'appflowy') }}",
              "type": "array"
            },
            {
              "id": "affine-changes",
              "name": "affineChanges",
              "value": "={{ $('Get Recent Changes').all().filter(item => item.source_system === 'affine') }}",
              "type": "array"
            },
            {
              "id": "sync-needed",
              "name": "syncNeeded",
              "value": "={{ $json.appFlowyChanges.length > 0 || $json.affineChanges.length > 0 || $('Process Sync Request').item.json.forceSync }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "analyze-sync-requirements",
      "name": "Analyze Sync Requirements",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        950,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "sync-needed-condition",
              "leftValue": "={{ $json.syncNeeded }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-sync-needed",
      "name": "Check Sync Needed",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1200,
        400
      ]
    },
    {
      "parameters": {
        "fieldsToSplitOut": "appFlowyChanges",
        "options": {}
      },
      "id": "process-appflowy-changes",
      "name": "Process AppFlowy Changes",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        1450,
        200
      ]
    },
    {
      "parameters": {
        "fieldsToSplitOut": "affineChanges",
        "options": {}
      },
      "id": "process-affine-changes",
      "name": "Process Affine Changes",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        1450,
        300
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "databaseId": "={{ $json.metadata.database_id }}",
        "rowId": "={{ $json.metadata.row_id }}",
        "options": {}
      },
      "id": "get-appflowy-content",
      "name": "Get AppFlowy Content",
      "type": "n8n-nodes-appflowy.database",
      "typeVersion": 1,
      "position": [
        1700,
        200
      ],
      "credentials": {
        "appFlowyApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "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.metadata.workspace_id }}",
            "docId": "={{ $json.metadata.doc_id }}"
          }
        }
      },
      "id": "get-affine-content",
      "name": "Get Affine Content",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1700,
        300
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "transformed-content",
              "name": "transformedContent",
              "value": "={{ $json.title + '\\n\\n' + $json.content }}",
              "type": "string"
            },
            {
              "id": "target-system",
              "name": "targetSystem",
              "value": "affine",
              "type": "string"
            },
            {
              "id": "content-metadata",
              "name": "contentMetadata",
              "value": "={{ { originalSource: 'appflowy', syncTimestamp: $now, originalId: $json.id, title: $json.title } }}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "id": "transform-appflowy-to-affine",
      "name": "Transform AppFlowy to Affine",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1950,
        200
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "transformed-content-affine",
              "name": "transformedContent",
              "value": "={{ $json.data.workspace.doc.title + '\\n\\n' + $json.data.workspace.doc.content }}",
              "type": "string"
            },
            {
              "id": "target-system-appflowy",
              "name": "targetSystem",
              "value": "appflowy",
              "type": "string"
            },
            {
              "id": "content-metadata-affine",
              "name": "contentMetadata",
              "value": "={{ { originalSource: 'affine', syncTimestamp: $now, originalId: $json.data.workspace.doc.id, title: $json.data.workspace.doc.title } }}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "id": "transform-affine-to-appflowy",
      "name": "Transform Affine to AppFlowy",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1950,
        300
      ]
    },
    {
      "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": "mutation CreateDocument($workspaceId: String!, $title: String!, $content: String!) { createDoc(workspaceId: $workspaceId, title: $title, content: $content) { id title createdAt } }",
          "variables": {
            "workspaceId": "{{ $vars.affine_workspace_id }}",
            "title": "{{ $json.contentMetadata.title }} (from AppFlowy)",
            "content": "{{ $json.transformedContent }}"
          }
        }
      },
      "id": "create-affine-document",
      "name": "Create Affine Document",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2200,
        200
      ]
    },
    {
      "parameters": {
        "operation": "create",
        "databaseId": "{{ $vars.appflowy_sync_database_id }}",
        "data": {
          "fields": [
            {
              "name": "title",
              "value": "={{ $json.contentMetadata.title }} (from Affine)"
            },
            {
              "name": "content",
              "value": "={{ $json.transformedContent }}"
            },
            {
              "name": "source",
              "value": "affine"
            },
            {
              "name": "original_id",
              "value": "={{ $json.contentMetadata.originalId }}"
            }
          ]
        },
        "options": {}
      },
      "id": "create-appflowy-page",
      "name": "Create AppFlowy Page",
      "type": "n8n-nodes-appflowy.database",
      "typeVersion": 1,
      "position": [
        2200,
        300
      ],
      "credentials": {
        "appFlowyApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "upsert",
        "table": {
          "__rl": true,
          "value": "knowledge_sync_mapping",
          "mode": "list"
        },
        "data": {
          "insert": [
            {
              "column": "source_system",
              "value": "appflowy"
            },
            {
              "column": "source_id",
              "value": "={{ $('Get AppFlowy Content').item.json.id }}"
            },
            {
              "column": "target_system",
              "value": "affine"
            },
            {
              "column": "target_id",
              "value": "={{ $json.data.createDoc.id }}"
            },
            {
              "column": "mapping_type",
              "value": "document"
            },
            {
              "column": "sync_direction",
              "value": "bidirectional"
            },
            {
              "column": "created_at",
              "value": "={{ $now }}"
            },
            {
              "column": "status",
              "value": "active"
            }
          ]
        },
        "options": {}
      },
      "id": "create-sync-mapping-affine",
      "name": "Create Sync Mapping (Affine)",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2450,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "upsert",
        "table": {
          "__rl": true,
          "value": "knowledge_sync_mapping",
          "mode": "list"
        },
        "data": {
          "insert": [
            {
              "column": "source_system",
              "value": "affine"
            },
            {
              "column": "source_id",
              "value": "={{ $('Get Affine Content').item.json.data.workspace.doc.id }}"
            },
            {
              "column": "target_system",
              "value": "appflowy"
            },
            {
              "column": "target_id",
              "value": "={{ $json.id }}"
            },
            {
              "column": "mapping_type",
              "value": "document"
            },
            {
              "column": "sync_direction",
              "value": "bidirectional"
            },
            {
              "column": "created_at",
              "value": "={{ $now }}"
            },
            {
              "column": "status",
              "value": "active"
            }
          ]
        },
        "options": {}
      },
      "id": "create-sync-mapping-appflowy",
      "name": "Create Sync Mapping (AppFlowy)",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2450,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "insert",
        "table": {
          "__rl": true,
          "value": "bidirectional_sync_log",
          "mode": "list"
        },
        "data": {
          "insert": [
            {
              "column": "sync_session_id",
              "value": "={{ $('Process Sync Request').item.json.syncTimestamp }}"
            },
            {
              "column": "source_system",
              "value": "={{ $('Process Sync Request').item.json.source }}"
            },
            {
              "column": "items_synced",
              "value": "={{ $('Analyze Sync Requirements').item.json.appFlowyChanges.length + $('Analyze Sync Requirements').item.json.affineChanges.length }}"
            },
            {
              "column": "sync_direction",
              "value": "={{ $('Process Sync Request').item.json.direction }}"
            },
            {
              "column": "status",
              "value": "completed"
            },
            {
              "column": "started_at",
              "value": "={{ $('Process Sync Request').item.json.syncTimestamp }}"
            },
            {
              "column": "completed_at",
              "value": "={{ $now }}"
            },
            {
              "column": "errors",
              "value": "{{ [] }}"
            }
          ]
        },
        "options": {}
      },
      "id": "log-sync-completion",
      "name": "Log Sync Completion",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2700,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "sync-summary",
              "name": "syncSummary",
              "value": "={{ { sessionId: $('Process Sync Request').item.json.syncTimestamp, itemsSynced: $('Analyze Sync Requirements').item.json.appFlowyChanges.length + $('Analyze Sync Requirements').item.json.affineChanges.length, direction: $('Process Sync Request').item.json.direction, status: 'completed', completedAt: $now } }}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "id": "prepare-response",
      "name": "Prepare Response",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2950,
        400
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "respond-to-sync-trigger",
      "name": "Respond to Sync Trigger",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3200,
        400
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "no-sync-message",
              "name": "message",
              "value": "No synchronization needed - no recent changes detected",
              "type": "string"
            },
            {
              "id": "sync-status",
              "name": "status",
              "value": "skipped",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "prepare-no-sync-response",
      "name": "Prepare No Sync Response",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1200,
        600
      ]
    },
    {
      "parameters": {
        "content": "## Bidirectional Knowledge Sync Between AppFlowy and Affine\n\n**Purpose:** Maintain synchronized content between AppFlowy and Affine workspaces with conflict resolution and mapping tracking.\n\n**Features:**\n- Bidirectional sync between AppFlowy databases and Affine documents\n- Content transformation and mapping\n- Conflict detection and resolution\n- Sync status tracking and logging\n- Force sync capability\n- Selective sync with filters\n\n**Sync Process:**\n1. Analyze recent changes in both systems\n2. Check for existing mappings and conflicts\n3. Transform content between formats\n4. Create/update content in target systems\n5. Create bidirectional mappings\n6. Log sync completion and metrics\n\n**API Endpoints:**\n- POST /webhook/trigger-knowledge-sync\n- Supports: source, direction, filter, forceSync parameters",
        "height": 600,
        "width": 800,
        "color": 7
      },
      "id": "bidirectional-sync-documentation",
      "name": "Bidirectional Sync Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        100,
        50
      ]
    }
  ],
  "connections": {
    "Knowledge Sync Trigger": {
      "main": [
        [
          {
            "node": "Process Sync Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Sync Request": {
      "main": [
        [
          {
            "node": "Get AppFlowy Mappings",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Affine Mappings",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Recent Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Recent Changes": {
      "main": [
        [
          {
            "node": "Analyze Sync Requirements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Sync Requirements": {
      "main": [
        [
          {
            "node": "Check Sync Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Sync Needed": {
      "main": [
        [
          {
            "node": "Process AppFlowy Changes",
            "type": "main",
            "index": 0
          },
          {
            "node": "Process Affine Changes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare No Sync Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process AppFlowy Changes": {
      "main": [
        [
          {
            "node": "Get AppFlowy Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Affine Changes": {
      "main": [
        [
          {
            "node": "Get Affine Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get AppFlowy Content": {
      "main": [
        [
          {
            "node": "Transform AppFlowy to Affine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Affine Content": {
      "main": [
        [
          {
            "node": "Transform Affine to AppFlowy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transform AppFlowy to Affine": {
      "main": [
        [
          {
            "node": "Create Affine Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transform Affine to AppFlowy": {
      "main": [
        [
          {
            "node": "Create AppFlowy Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Affine Document": {
      "main": [
        [
          {
            "node": "Create Sync Mapping (Affine)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create AppFlowy Page": {
      "main": [
        [
          {
            "node": "Create Sync Mapping (AppFlowy)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Sync Mapping (Affine)": {
      "main": [
        [
          {
            "node": "Log Sync Completion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Sync Mapping (AppFlowy)": {
      "main": [
        [
          {
            "node": "Log Sync Completion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Sync Completion": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Response": {
      "main": [
        [
          {
            "node": "Respond to Sync Trigger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare No Sync Response": {
      "main": [
        [
          {
            "node": "Respond to Sync Trigger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bidirectional-sync-v1",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "BidirectionalKnowledgeSync",
  "tags": [
    "knowledge-management",
    "sync",
    "bidirectional",
    "appflowy",
    "affine"
  ]
}

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

Bidirectional Knowledge Sync Between AppFlowy and Affine. Uses postgres, n8n-nodes-appflowy, httpRequest. Webhook trigger; 22 nodes.

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

More Data & Sheets workflows → · Browse all categories →

Related workflows

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

Data & Sheets

Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.

HTTP Request, Postgres, @Apify/N8N Nodes Apify
Data & Sheets

Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.

HTTP Request, Postgres, Error Trigger
Data & Sheets

Mercanta — WA Bot. Uses postgres, httpRequest. Webhook trigger; 24 nodes.

Postgres, HTTP Request
Data & Sheets

This n8n workflow automates the transformation of raw text ideas into structured visual diagrams and content assets using NapkinAI.

HTTP Request, Email Send, Postgres
Data & Sheets

Receive request via webhook with customer question Analyze sentiment and detect urgency using JavaScript Send urgent alerts to Slack for critical cases Search knowledge base and fetch conversation his

HTTP Request, Postgres, Email Send +1