Integration Guide · N8N + Neo4j

N8N + Neo4j Integration Guide

Published 2026-09-01 · Agentic Giants · 12 min read

TL;DR

N8N has no native Neo4j node, but the HTTP Request node connects cleanly to Neo4j's HTTP query API. Set up an HTTP Header Auth credential, POST Cypher statements to /db/neo4j/tx/commit, parse the JSON response in a Function node, and branch on the result with IF or Switch nodes. Write outcomes back with MERGE so re-running the workflow never duplicates data. The result: every workflow decision is grounded in your knowledge graph instead of whatever payload happened to arrive in the trigger.

What you'll build

By the end of this guide you will have a working N8N Neo4j integration: an N8N workflow that queries a Neo4j knowledge graph for context before it decides what to do, and writes the outcome of that decision back into the graph when it finishes. Concretely, the workflow will:

  • Authenticate against Neo4j from N8N using a reusable credential.
  • Run a parameterized Cypher query against the graph from an HTTP Request node.
  • Parse the graph's response and use it to branch the workflow's logic with IF and Switch nodes.
  • Write the workflow's outcome back into Neo4j as new nodes or relationships, using MERGE so re-runs stay idempotent.

This is the same architecture we described at a conceptual level in Neo4j + N8N: Building Knowledge-Aware Automation Workflows. This guide is the implementation: the actual node configuration, request bodies, and Cypher you need to make it run.

Prerequisites

You need four things in place before you start:

  • An N8N instance — self-hosted via Docker or N8N Cloud. Either works for everything in this guide; only the optional Bolt driver approach in Step 1 requires a self-hosted instance with the ability to install npm packages.
  • A Neo4j instance — Neo4j Aura (managed cloud) or a self-hosted Neo4j server with the HTTP connector enabled (it is on by default).
  • Basic Cypher — you should be comfortable reading and writing simple MATCH, MERGE, and CREATE statements. You don't need to be an expert; this guide shows the exact patterns you'll reuse.
  • Familiarity with N8N's HTTP Request node — knowing how to set a method, URL, headers, and JSON body will make the rest of this guide move quickly.

Step 1: Configure Neo4j access

N8N does not ship a dedicated Neo4j node, so the connection runs through N8N's general purpose HTTP Request node talking to Neo4j's built-in HTTP query API. That API accepts Cypher over plain HTTP and returns JSON, which means no custom package installation and no driver version to keep in sync — it works identically on N8N Cloud and self-hosted N8N.

Create the credential once and reuse it across every workflow:

  • In N8N, go to Credentials → New → HTTP Header Auth.
  • Name it something like Neo4j Aura – Production.
  • Set the header name to Authorization.
  • Set the header value to Basic <base64(username:password)>. Neo4j's HTTP API uses HTTP Basic Auth, so the value is the literal word Basic followed by your username:password pair, base64 encoded.

Your Neo4j base URL depends on how it's hosted:

  • Neo4j Aura: https://<instance-id>.databases.neo4j.io
  • Self-hosted: http://<host>:7474 (or your configured HTTP connector port)

If you eventually need Bolt instead of HTTP — for very high-throughput workflows or long-lived transactions — you can drop into an N8N Code node and use the neo4j-driver npm package directly. That path only works on self-hosted N8N where you control the container's installed packages; for everything in this guide, the HTTP API is the simpler and equally capable choice.

Step 2: Query Neo4j from N8N

Add an HTTP Request node to your workflow and configure it to call Neo4j's transactional Cypher endpoint:

  • Method: POST
  • URL: https://<your-neo4j-host>/db/neo4j/tx/commit
  • Authentication: the HTTP Header Auth credential from Step 1
  • Headers: Content-Type: application/json
  • Body: JSON, matching the structure below

The request body Neo4j expects is a statements array:

{
  "statements": [
    {
      "statement": "MATCH (c:Customer {email: $email}) RETURN c.name AS name, c.tier AS tier, c.riskScore AS riskScore",
      "parameters": {
        "email": "{{$json.customerEmail}}"
      }
    }
  ]
}

Two details matter here. First, always use parameters for values coming from the workflow — never build the Cypher string by concatenating incoming data. Parameterized queries avoid injection and let Neo4j reuse a cached query plan. Second, the endpoint is /db/<database-name>/tx/commit — replace neo4j with your actual database name if you're not using the default.

Neo4j's response has a predictable shape:

{
  "results": [
    {
      "columns": ["name", "tier", "riskScore"],
      "data": [
        { "row": ["Acme Corp", "enterprise", 0.12] }
      ]
    }
  ],
  "errors": []
}

The columns array names each returned field in order; each entry in data holds one matching record as a row array in that same order. Always check errors — a Cypher syntax mistake returns HTTP 200 with an error object inside the body, not an HTTP error status, so your workflow needs to inspect it explicitly rather than relying on N8N's built-in HTTP failure handling.

Step 3: Use graph results in workflow logic

Raw Neo4j responses are awkward to branch on directly, since the data arrives as parallel columns and row arrays rather than named fields. Add a Function (or Code) node right after the HTTP Request node to flatten it:

const result = items[0].json.results[0];
const errors = items[0].json.errors;

if (errors && errors.length > 0) {
  throw new Error('Neo4j query error: ' + errors[0].message);
}

if (!result || result.data.length === 0) {
  return [{ json: { found: false } }];
}

const columns = result.columns;
const row = result.data[0].row;

const record = {};
columns.forEach((col, i) => { record[col] = row[i]; });
record.found = true;

return [{ json: record }];

The Function node now outputs a plain object — { found: true, name: "Acme Corp", tier: "enterprise", riskScore: 0.12 } — that downstream nodes can reference normally. Feed that into an IF node to gate on a single condition (does this customer exist in the graph at all), or a Switch node to route on a multi-valued field like tier, sending enterprise accounts down one branch, self-serve accounts down another, and unknown emails into a lead-creation branch.

This is the step that turns a generic workflow into a knowledge-aware one: the branching decision is grounded in verified graph state rather than whatever a form or webhook payload happened to claim.

Step 4: Write back to Neo4j

Once the workflow has acted — sent an email, updated a CRM record, escalated a ticket — write the outcome back into the graph so the next workflow run (and every other consumer of the graph) sees current state. Add a second HTTP Request node, same endpoint and credential as Step 2, with a write statement in the body.

For anything that might already exist, use MERGE, which matches on a key and only creates what is missing — safe to re-run without duplicating data:

{
  "statements": [
    {
      "statement": "MERGE (c:Customer {email: $email}) SET c.lastContacted = datetime(), c.tier = $tier MERGE (c)-[:HANDLED_BY]->(w:Workflow {name: $workflowName})",
      "parameters": {
        "email": "{{$json.customerEmail}}",
        "tier": "{{$json.tier}}",
        "workflowName": "lead-routing-v2"
      }
    }
  ]
}

Reserve CREATE for records where duplicates are expected and desirable — an event log or audit trail of every workflow execution, for example, where you want a new node every time:

{
  "statements": [
    {
      "statement": "MATCH (c:Customer {email: $email}) CREATE (c)-[:TRIGGERED]->(e:WorkflowRun {timestamp: datetime(), outcome: $outcome})",
      "parameters": {
        "email": "{{$json.customerEmail}}",
        "outcome": "{{$json.outcome}}"
      }
    }
  ]
}

The rule of thumb: MERGE for state (entities, relationships that represent current facts), CREATE for events (things that happened at a point in time and should accumulate).

Common patterns

Three patterns cover most production N8N Neo4j integrations we build:

  • Enrich before you route. A webhook or form trigger fires with minimal data (an email, a phone number). The workflow's first move is a Neo4j query to pull full context — account tier, past interactions, related entities — before any routing decision is made. This is the pattern from Steps 2 and 3 above, and it's the single highest-leverage use of an N8N Neo4j integration.
  • Update the graph after external events. A CRM update, a form submission, or a support ticket closing triggers a workflow whose sole job is to write that event back into the graph — new relationships, updated properties — so the graph never drifts out of sync with your systems of record.
  • Scheduled graph maintenance. A Cron-triggered N8N workflow runs periodic Cypher jobs against Neo4j: expiring stale relationships, recalculating a derived score, or reconciling nodes against a source system. This keeps the graph healthy without hand-run maintenance scripts.

For the broader architecture these patterns sit inside — how Neo4j, N8N, and the rest of an agentic automation stack fit together — see The Complete Guide to Intelligent Automation. When agents need governed access to the graph and other enterprise systems, we layer in MCP servers that control exactly which operations each agent can perform see how we shipped 10 production MCP servers for Optevo using this approach.

Troubleshooting

  • 401 Unauthorized. Almost always a malformed Authorization header. Confirm the header value is literally Basic followed by a space and the base64 encoding of username:password — a common mistake is base64-encoding just the password, or forgetting the Basic prefix entirely.
  • Cypher syntax errors that don't fail the HTTP call. Neo4j's HTTP API returns 200 with the problem inside the errors array in the response body, not as an HTTP error code. If a query silently "does nothing," check errors before assuming the request succeeded — the Function node in Step 3 already does this check for you.
  • Large result sets stalling the workflow. A query without a LIMIT clause against a large graph can return thousands of rows and blow past N8N's memory or the HTTP Request node's response size handling. Add LIMIT to exploratory queries, and paginate with SKIP/LIMIT for anything that legitimately needs to process a large set.
  • Timeouts on long-running queries. Raise the HTTP Request node's timeout setting (found under Options) for queries that traverse deep or wide parts of the graph, and consider whether the underlying Cypher can be rewritten with a tighter MATCH pattern or an index instead of relying on a longer timeout.

FAQ

Frequently asked questions

Can N8N connect directly to Neo4j?

Yes. N8N has no dedicated Neo4j node, so the standard approach is the built-in HTTP Request node calling Neo4j's HTTP query API (the /db/{database}/tx/commit endpoint), authenticated with an HTTP Header Auth credential carrying a base64 encoded username and password. This works against both self-hosted Neo4j and Neo4j Aura, and needs no custom package installation.

Do I need the Bolt protocol or is HTTP enough for N8N Neo4j integration?

HTTP is enough for the vast majority of N8N workflows. The HTTP query API supports the full Cypher language, transactions, and parameters. Bolt is faster for high-throughput or long-running sessions, but it requires a custom Code node with the neo4j-driver package, which adds a dependency N8N Cloud cannot install. Start with HTTP; move to Bolt only if you hit measurable latency problems.

How do I pass a Cypher query with parameters from N8N?

Send a POST request to /db/neo4j/tx/commit with a JSON body containing a statements array. Each statement object has a statement field (the Cypher text) and a parameters object. Always use parameters instead of string-concatenating values into the Cypher text — it avoids injection risk and lets Neo4j cache the query plan.

How do I branch an N8N workflow based on what Neo4j returns?

Parse the HTTP Request node's response in a Function (or Code) node: Neo4j's HTTP API returns a results array with columns and data.row for each matching record. Extract the fields you need into a flat object, then feed that into an IF or Switch node to route the workflow based on whether the graph returned a match, a risk flag, a tier, or any other property.

What is the safest way to write data back to Neo4j from N8N?

Use MERGE rather than CREATE for anything that might already exist — MERGE matches on a unique key and only creates the node or relationship if it is missing, which makes the workflow safe to re-run without duplicating data. Reserve CREATE for events and log-style nodes where duplicates are expected and desired, such as an audit trail of workflow runs.

N8N + Neo4j, built and hardened for production

Get expert help with your N8N + Neo4j integration

We design, build, and operate N8N-to-knowledge-graph integrations for enterprises — credential management, error handling, and write-back patterns included, deployed in your environment and owned by your team.

Get expert help →