The Neo4j Python driver: Bolt, two maintained lines, and a renamed package
Neo4j Bolt driver for Python
At a glance
- What is it?
- The official Neo4j driver for Python speaks Bolt, supports Python 3.10 to 3.15, and is installed as `neo4j` because the old `neo4j-driver` name stopped being updated at 6.0.0. Two lines are maintained at once, the current one and a 5.28.8 long term support release.
- Who is it for?
- The driver fits a Python team that talks to a Neo4j server over Bolt and wants a typed, maintained client with an async API and a documented compatibility story. It is the wrong choice for a team that wants an object mapper or a query builder on top of a graph, because the driver is a protocol client and stops at the Cypher boundary, and the optional Rust extensions for better performance are not installed by default, so the pure Python path is the default.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The official driver, with a long term support line beside the current one
The neo4j-python-driver repository contains the official Neo4j driver for Python, a Bolt driver, and its first commitment is a compatibility promise: driver upgrades within a major version will never contain breaking API changes.
That promise is what makes the release list readable. The recent releases are Neo4j Python Driver 6.4.1 from 2026-10-15, 5.28.8 from 2026-10-15 and marked as an LTS release, and 6.4.0 from 2026-10-05. So there are two maintained lines at once, the current 6.x line and a 5.28.8 long term support release, and both were published on the same day, which is what a vendor doing support properly looks like.
The 5.x line exists for deployments that do not want to move their driver version. The 6.x line is where new work goes. A team that wants the newest features takes 6.x, and a team that wants a version that will not change for a while takes the LTS line, and neither has to guess when the other one disappears.
The compatibility that matters is not only with Python but with the server, and the README sends readers to the Neo4j supported versions page for that, which is the right way round. A driver and a server are released independently, so the question is always which combination is supported.
The Python support is explicit: 3.15, 3.14, 3.13, 3.12, 3.11 and 3.10 are all supported, and the project metadata sets `requires-python` to `>=3.10`.
The default branch is `6.x`, the last push was on 2026-10-15, the primary language is Python, and the licence is a dual one that the repository explains in three separate files.
The package was renamed, and the old name is frozen
Installation is one command:
pip install neo4jAnd there is a note attached to it that matters more than most install notes, because getting it wrong means installing a package that no longer receives updates.
`neo4j-driver` is the old name for the package. It is now deprecated and will receive no further updates starting with 6.0.0, and the instruction is to install `neo4j` as shown above. The source of the note is visible in the file itself, where a comment above it says to remove this note in 7.0, so it is a migration aid with an end date rather than permanent guidance.
The failure mode for a team that ignores it is quiet. An old `neo4j-driver` install keeps working, keeps resolving imports under the old name, and simply stops receiving the fixes that a 6.x driver would get, which is exactly the kind of problem that surfaces months later during an incident.
There is a second installation route, and it is not part of the default install. The README points at separate Rust extensions for this driver for better performance, and says plainly that the Rust extensions are not installed by default, with a link to the neo4j-python-driver-rust-ext repository. The distinction is important: a user who wants the faster path is not adding a dependency, they are building an extension against the driver.
execute_query is the shape of the API the example teaches
The quick example in the README is the clearest statement of how the driver expects to be used, and it is short enough to read in full.
from neo4j import GraphDatabase, RoutingControl
URI = "neo4j://localhost:7687"
AUTH = ("neo4j", "password")
def add_friend(driver, name, friend_name):
driver.execute_query(
"MERGE (a:Person {name: $name}) "
"MERGE (friend:Person {name: $friend_name}) "
"MERGE (a)-[:KNOWS]->(friend)",
name=name, friend_name=friend_name, database_="neo4j",
)
with GraphDatabase.driver(URI, auth=AUTH) as driver:
add_friend(driver, "Arthur", "Guinevere")Four things are visible in it. The driver is a context manager, so the connection lifecycle is a block rather than something to remember to close. The query is parameterised, with `$name` placeholders and a keyword argument for the value, which is the correct way to send data into Cypher. The call takes a `database_` argument, so a multi-database server is addressed explicitly. And the read in the fuller example takes a `routing_=RoutingControl.READ` argument, which tells the driver to route the query to a follower rather than to the leader, and that distinction is exactly what a clustered deployment needs.
The return value is a three-tuple, since the fuller example destructures it as `records, _, _`, and the two discarded values are the summary of the query and the keys of the result, which is a useful reminder that a Cypher query returns a result set and a summary of what the server did.
The Cypher in the example is worth pausing on, because it is the graph idiom in three lines: `MERGE` for each node by a property, and `MERGE` for the relationship between them, which makes the whole example idempotent.
Async and typing are in the metadata, not an afterthought
The project classifiers are short and they tell you the shape of the library: `Framework :: AsyncIO`, `Typing :: Typed`, `Development Status :: 5 - Production/Stable`, `Operating System :: OS Independent`, and one classifier per supported Python version.
The async classifier is the one that matters most for a driver, because a graph query in a web application is almost always waiting on the network, and an async driver turns a blocking call into something an event loop can schedule. A driver that is sync only forces a thread per query, which is exactly the cost a modern Python service is trying to avoid.
`Typing :: Typed` says the package ships type information, and for a driver whose whole surface is a set of records, dictionaries and optional values, that is what makes the difference between a query result that a type checker can help with and a dictionary of `Any`. The `neo4j` package metadata also declares the `Framework :: AsyncIO` classifier alongside the typing one, which suggests the two are supported together rather than as separate products.
The single runtime dependency is `pytz`, and that is itself a dated choice worth a note. A time zone library pinned to a specific set of releases is the kind of dependency that ages quietly, and it is on the critical path of every installation of the driver. The rest of the runtime story is the standard library and the Bolt protocol implementation inside the package itself, with the Rust extension as the optional addition.
The further reading that the README recommends is a sensible reading list: the Neo4j Operations Manual for running a server, the Python Driver Manual as a good introduction to the driver, the API documentation for the full reference, the Cypher cheat sheet for the query language, an example project that is a small web application using the driver, the interactive GraphAcademy training, the driver wiki which includes the change logs, and the migration guide.
The performance path is a separately built extension
The alternative installation section is short and it is the part of the README with a real engineering decision in it.
You may want to have a look at the available Rust extensions for this driver for better performance, and the Rust extensions are not installed by default, with the information in the neo4j-python-driver-rust-ext repository.
So the driver has two shapes. The default is pure Python, which installs with a single pip command and behaves identically on every platform, and the alternative is a compiled extension that a team builds and installs itself, which is the path to the throughput numbers that the pure Python path cannot reach.
That division is a deliberate one and it has consequences. The extension is not installed by default, so enabling it is part of the deployment rather than part of the install, and a deployment that uses it has a build matrix that the pure Python one does not. A team that reaches for the extension usually does so because it has profiled a query path and knows where the time goes, and a team that reaches for it because the documentation mentions it will find it a lot of work for a modest gain.
The other thing the repository shows is how seriously the project treats the parts of a driver that are not the protocol. The `testkit/` and `testkitbackend/` directories are the shared test kit and the server side of it, which is how a driver is tested against a real server rather than against a mock, and `benchkit/` with `benchmarks/` is the performance harness that produces the numbers the extension is meant to improve. There is a `tox.ini` for the test environments, a `.pre-commit-config.yaml` for the checks, a `.semgrepignore`, a `TESTING.md`, and a `CHANGELOG.md`.
A dual licence, and why GitHub cannot classify it
The licence field in the project metadata reads `Apache-2.0 AND Python-2.0`, and the repository root explains that with three files: `LICENSE.APACHE2.txt`, `LICENSE.PYTHON.txt` and `LICENSE.txt`, plus a `NOTICE.txt`.
That is the standard dual-licence arrangement for a library that wants to be usable both as an Apache-licensed work and under the Python Software Foundation licence, and it is the reason GitHub's classifier shows a custom licence rather than one of the standard identifiers. The choice of a dual licence is a deliberate distribution decision, and for a company it is one of the easier ones to accept, since either of the two terms is well understood.
What a reviewer should check is which files each term covers, and the presence of the separate `LICENSE.txt` file and the `NOTICE.txt` suggests the arrangement is spelled out rather than left to inference.
The rest of the repository root is the standard packaging set, with `pyproject.toml` for the build, `MANIFEST.in` for the source distribution, `docs/` for the documentation, `src/` for the package itself, and `bin/`. The `NOTICE.txt` file is the attribution notice that Apache-2.0 requires to be reproduced, and it is present, which is the kind of small correctness that a project with a compliance-conscious vendor gets right.
Where the driver is the wrong tool
Three limits are worth naming, and all three come from the driver being a protocol client rather than a data access layer.
The first is that it stops at Cypher. The driver sends queries and returns records, and it has no notion of an entity, a repository or a unit of work. A team that wants objects rather than tuples of dictionaries has to build the mapping itself, and the example project linked from the README is the place to see what that involves. The alternative is a graph ORM or a query builder, which trades the directness of Cypher for a layer that hides it, and the choice depends on whether the shape of the data is a domain model or a graph.
The second is the version matrix, and it is genuinely two-dimensional. The driver line and the server line are supported independently, and the README's answer to the compatibility question is a link to the supported versions page rather than a table. That is the correct answer for a vendor with a support policy, and it means an adopter has to check the combination rather than assume the latest driver talks to the latest server.
The third is the performance path, which is a build rather than an install. A deployment that wants the Rust extension owns that build, its toolchain and its platform matrix, and the pure Python path remains the fallback with different characteristics. A team that measures first and then decides is in a good position; a team that reaches for the extension early pays for it twice.
The practical alternative, for a team that wants stability rather than the newest features, is the 5.28.8 long term support line, which the vendor is publishing on the same day as the current one. The difference in approach between the two lines is small in code and large in policy, and choosing the LTS line is a decision about who absorbs the upgrade work later.
Editorial conclusion
The driver fits a Python team that talks to a Neo4j server over Bolt and wants a typed, maintained client with an async API and a documented compatibility story. It is the wrong choice for a team that wants an object mapper or a query builder on top of a graph, because the driver is a protocol client and stops at the Cypher boundary, and the optional Rust extensions for better performance are not installed by default, so the pure Python path is the default. Before adopting it, check the server compatibility page for the driver line you pick, because the driver and the server versions are supported independently, and read the migration guide if you are moving from the deprecated `neo4j-driver` package, because the package name changed and the old one no longer receives updates.
Frequently asked questions
How do I install the Neo4j Python driver?
With `pip install neo4j`. The old package name `neo4j-driver` is deprecated and will receive no further updates starting with 6.0.0, so installing it by mistake leaves you on a frozen version. There are also separate Rust extensions for better performance, which are not installed by default.
Which Python versions does the Neo4j Python driver support?
Python 3.10 through 3.15, and the package metadata sets requires-python to >=3.10. The classifiers list one entry per supported version, alongside Framework :: AsyncIO, Typing :: Typed and Development Status :: 5 - Production/Stable.
How do I run a query with the Neo4j Python driver?
Through driver.execute_query, which takes the Cypher text, keyword arguments for the parameters, and options such as database_ for the target database and routing_ with RoutingControl.READ to route to a follower. The driver itself is created with GraphDatabase.driver(URI, auth=AUTH) and used as a context manager.
Does the Neo4j Python driver have an async API?
The package classifiers include Framework :: AsyncIO alongside Typing :: Typed, so the async interface is part of the supported surface rather than an extra. The Python Driver Manual linked from the README is described as a good introduction to the driver.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/neo4j-neo4j-python-driver)Was this useful?