# pgGraph indexes your foreign keys into a graph schema, and does not use SQL/PGQ at all

> pgGraph is a Rust extension for PostgreSQL that builds a derived graph index over ordinary tables and exposes it through functions in a graph schema. It supports PostgreSQL 14 through 18, ships four install channels that drift apart, and requires every graph to be rebuilt on upgrade.

**Evokoa/pgGraph** — Open-source graph database superpowers for your existing Postgres data.

- Repository: https://github.com/Evokoa/pgGraph
- Website: https://www.polygres.com
- Stars: 1,061 · Forks: 85
- Language: Rust
- License: NOASSERTION
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/evokoa-pggraph

## Graph queries run over your tables through functions in a graph schema

The pitch is that your tables stay where they are. pgGraph builds a derived graph index over ordinary PostgreSQL tables and exposes it to SQL through functions in a graph schema, so graph search, traversal, shortest path and relationship queries do not need a second database, a graph-specific storage engine, or a new query language. The reason given for wanting it is concrete rather than abstract: questions like finding records related to a given person within two hops, or finding the shortest path between a person and a company, otherwise each need their own recursive SQL written against that schema.

The quickstart shows the smallest possible version of that idea. It creates two normal PostgreSQL tables, discovers the foreign key relationship between them, builds the graph from what it found, and runs example queries against it. So the edges are not something you declare in a graph schema by hand. They come from constraints your relational model already has, which is why the discovery step is part of the demo rather than an optional extra.

Version 1.2.0 widened that model further by making relationship types open-vocabulary, which is what lets an edge carry a label your existing foreign keys never had. The practical shape is a Postgres database with one more schema in it, a rebuild step whenever the underlying data changes, and queries that stay in the same transaction boundary as everything else.

## The upstream standard it avoided was reverted from the PostgreSQL 19 branch

The most consequential paragraph in the file is a note, and it concerns SQL/PGQ. PostgreSQL removed SQL/PGQ from the PostgreSQL 19 release branch on 2026-09-07, with the file linking the revert commit and the release-management discussion. pgGraph supports PostgreSQL 14 through 18 and states that it does not depend on native SQL/PGQ. The last sentence is the one to sit with: future native integration has no committed PostgreSQL target version.

That is an unusual position to be in and an honest one to write down. A project whose entire value is graph queries inside Postgres would normally want the standard graph-query language that upstream was preparing, and the answer here is that the standard is gone from the release branch and no replacement version is promised. Everything pgGraph offers runs through its own functions instead, so nothing about the extension depends on a language decision that upstream has already reversed.

The release titles around it read consistently. v1.1.0, published 2026-08-16, is caller-scoped row level security and safe replacement. v1.2.0, published 2026-08-22, is open-vocabulary relationship types. v1.2.1, published 2026-09-23, is a stability release. The last push to the repository was on 2026-09-23 and it is not archived.

## Upgrading means rebuilding every graph, and pg_cron runs the maintenance

The upgrade note is stated without softening: upgrading requires updating the extension and rebuilding each graph, with details in the release notes. For a derived index that is the expected cost, but it is a cost that lands on data size rather than on downtime, and it is worth sizing before you commit to a graph over a large table.

The maintenance itself is automated through PostgreSQL's job scheduler. The verification query the quickstart runs asks for both extensions by name:

```bash
docker exec pggraph psql -U postgres -d graph \
  -c "SELECT extname, extversion FROM pg_extension WHERE extname IN ('graph', 'pg_cron');"
```

pg_cron appearing next to graph in that list is the mechanism. It also explains the one failure mode the file bothers to name: if the image's scheduled maintenance is already running, the demo waits for that bounded operation to finish before building, rather than failing with a transient lock. A script that waits on a known background job and says so is worth more than one that reports a lock error and moves on.

Isolation is the other theme in these releases. Caller-scoped row level security in v1.1.0 and database isolation in v1.2.1 together suggest that graph queries were taught to respect the same access rules as the tables underneath them, which is the right thing to get right before a graph view starts returning rows a direct query would have refused.

## Four install channels, updated on separate schedules

There are four ways in, and they are not kept in step with each other. The repository quickstart builds a disposable PostgreSQL 17 image. The published container image is versioned and multi-architecture, ghcr.io/evokoa/pggraph:1.2.1, with an instruction to verify its published digest before deployment. The Homebrew tap at Evokoa/tap is described as the convenience channel for local PostgreSQL 17 extension installs:

```bash
brew tap Evokoa/tap
brew install pggraph
brew test pggraph
```

Fourth is PGXN, which takes a verified source ZIP from the signed 1.2.1 release bundle and, because pgGraph is a Rust and pgrx extension, needs the Rust toolchain as well as PostgreSQL development headers and pg_config. The toolchain version is pinned at 1.96 by graph/rust-toolchain.toml.

The channel drift is acknowledged rather than hidden. The Homebrew tap is updated separately from the Docker and PGXN channels, and the instruction for this release is to verify that the installed extension reports version 1.2.1. Two other defaults are worth knowing: images exist for PostgreSQL 14 through 18, and a tag with no major version in it, such as 1.2.1 or latest, resolves to the PostgreSQL 17 image. PostgreSQL 13 is no longer an official support target after upstream end of life, though the legacy pg13 pgrx feature is still available on a best-effort basis.

## The quickstart compose file publishes 5432 with the password postgres

The compose file that backs the demo is short, and every default in it is chosen for a throwaway database:

```yaml
ports:
  - "${PGGRAPH_QUICKSTART_PORT:-5432}:5432"
environment:
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: postgres
  POSTGRES_DB: graph
```

Port 5432 is published to the host with no host prefix, so it binds on every interface, and the password is the username. Both values are overridable, since the image and the port are read from PGGRAPH_QUICKSTART_IMAGE and PGGRAPH_QUICKSTART_PORT, but nothing in the file suggests that for a quickstart. The data lives in a named volume called pgdata, so a clean run is a matter of removing the volume, which is what the clean mode of the script does.

For a disposable demo that is the right trade-off: no credentials to invent, nothing to remember afterwards. It becomes the wrong trade-off the moment anyone points a real client at the host, and the file gives no signal that the two cases are different. If you intend to use this compose file as the base for anything persistent, change the password and bind the port to loopback before the first run rather than after.

The quickstart script itself wraps that file in seven modes, from the default demo through setup, psql, install into an existing container, a pgrx source install, a Streamlit playground and clean. The playground takes a preset dataset and a projection mode:

```bash
scripts/quickstart.sh playground panama csr
scripts/quickstart.sh playground panama mutable
```

which is a hint that the graph can be held either as a compressed row structure or as something you mutate in place. The script runs from a normal terminal on macOS and Linux, and on Windows only from WSL2 or Git Bash with Docker Desktop, and it says plainly that it is not a native PowerShell or Command Prompt script.

## The Makefile derives its own feature flag from pg_config

Because pgGraph is a pgrx extension, the build has to know which PostgreSQL major it is compiling against, and getting it wrong fails late. The Makefile takes care of that itself. It sets EXTENSION and CRATE_DIR to graph, allows PG_CONFIG to be overridden with pg_config as the default, and then derives the major version by running pg_config --version and keeping the digits. The install target then runs cargo pgrx install with the matching pgNN feature and no default features.

The comment above that derivation is the useful part. Without it, pgrx quietly builds its default, which is pg17, and the install fails on any other major. So the flag is not a convenience, it is the difference between an install that works and one that does not. The same constraint is restated for users rather than builders: the PostgreSQL major version of the extension package must match the target server.

There is one more piece of history in the file. The all target exists for PGXN compatibility and simply delegates to install, because cargo pgrx package needs a fully initialised pgrx environment that pgxn-client temporary directories do not provide. A target that exists only to satisfy an external packager, and says so in a comment, is a good sign about how the build has been forced to bend around tooling.

## Base images are pinned by digest, and the build refuses other CPU architectures

The Dockerfile pins both of its base images by content rather than by tag, taking rust 1.96.0 on bookworm and postgres 17 on bookworm as ARG defaults with sha256 digests attached. Versions for the rest of the toolchain are pinned the same way, with PGRX_VERSION at 0.19.1 and SFW_VERSION at 1.13.1. The apt step installs the PostgreSQL server and development packages for the requested major, with retries configured on the apt calls themselves.

One build dependency is fetched as a prebuilt binary rather than compiled. The script switches on TARGETARCH, selects either an arm64 or an x86_64 asset, and checks it against a sha256 written into that branch of the script, so the checksum travels with the architecture rather than with a manifest. Any architecture other than those two hits a branch that writes an unsupported architecture message and exits with status 2. In other words the container build is deliberately limited to two CPU architectures and fails loudly everywhere else.

Set against that care, the release instruction for the published image is one line: verify its digest before deployment. The project's own build pins digests, so the advice it gives adopters is the same standard it holds itself to. The remaining platform caveat lives at the other end of the version range, where PostgreSQL 13 stays available through the legacy pgrx feature on a best-effort basis only.

## Licence metadata says nothing while the root carries LICENSE and NOTICE

The licence is the one place where the repository contradicts itself in a way a reader cannot resolve from the file. No licence value appears in the repository's own metadata, and the root of the tree carries both a LICENSE file and a NOTICE. Nothing in the visible text says which terms apply to which part, or whether the two files agree with each other, so rather than pick one, treat the question as open: if the licence matters to your use, read both files at the root and confirm with the project rather than assuming the absence of a metadata value means the absence of terms.

The rest of the root describes a project with a fairly complete set of process files. There is SECURITY.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md and AGENTS.md, a second README in Chinese, a .gitleaksignore sitting next to a .gitignore, an .envrc for direnv alongside flake.nix and flake.lock for a Nix development environment, META.json, and directories for the Rust crate, the container assets, the release material, a sandbox, scripts and docs. The gitleaks exclusion list is worth a second look in particular, because a hand-maintained ignore file is the place where a secret scanner's findings get suppressed.

The public face also splits in two. The repository's homepage field points at polygres.com, and the same domain appears in the file as a managed offering for graph retrieval on Postgres, described as a launch separate from the open-source extension. Anyone arriving from the homepage therefore meets the commercial product first and the extension second.

## Conclusion

pgGraph is worth evaluating when your graph questions live inside relational data you cannot move, because it indexes foreign-key relationships into a derived graph in the same database and leaves your tables as the source of truth. Weigh three things first. It supports PostgreSQL 14 through 18, and the extension package's major version has to match the target server, so a mismatch is an install failure rather than a warning. Upgrading means rebuilding every graph rather than only updating the extension. And the quickstart compose file publishes port 5432 with the password postgres, which is right for a disposable demo and wrong for a database you intend to keep. If you would rather not run the extension yourself, the maintainers offer a managed version at polygres.com.

## FAQ

### Which PostgreSQL versions does pgGraph support?

Images are published for PostgreSQL 14 through 18, and a tag without a major version, such as 1.2.1 or latest, resolves to the PostgreSQL 17 image. PostgreSQL 13 is no longer an official target after upstream end of life, though the legacy pg13 pgrx feature remains available on a best-effort basis. The extension package's major version must match the target server.

### Does pgGraph depend on PostgreSQL's SQL/PGQ feature?

No. PostgreSQL removed SQL/PGQ from the 19 release branch on 2026-09-07, and pgGraph supports PostgreSQL 14 through 18 without depending on native SQL/PGQ. The write-up also notes that future native integration has no committed PostgreSQL target version.

### How does pgGraph build a graph from my existing tables?

Your tables stay the source of truth and pgGraph builds a derived index, queried through functions in the graph schema. The quickstart creates two ordinary tables, discovers the foreign key relationship between them, builds the graph and runs example queries against it.

### What happens to existing graphs when I upgrade pgGraph?

Upgrading requires updating the extension and rebuilding each graph, with the details in the release notes. Version 1.2.1 is described as a stability release focused on stability, synchronization and database isolation, following v1.1.0's caller-scoped row level security and safe replacement.

### How do I confirm which pgGraph version is installed?

Query pg_extension for the graph extension, and pg_cron alongside it, since the maintenance scheduler is a separate extension. The Homebrew tap is updated separately from the Docker and PGXN channels, so the file asks you to verify that the installed extension reports 1.2.1.

### What does the pgGraph quickstart expose to the network?

The compose file publishes 5432 to the host with POSTGRES_USER postgres, POSTGRES_PASSWORD postgres and POSTGRES_DB graph, storing data in a named pgdata volume. The image and port can be overridden with PGGRAPH_QUICKSTART_IMAGE and PGGRAPH_QUICKSTART_PORT.

## Sources

- [Evokoa/pgGraph on GitHub](https://github.com/Evokoa/pgGraph)
- [Issues](https://github.com/Evokoa/pgGraph/issues)
- [Project website](https://www.polygres.com)
- [README](https://github.com/Evokoa/pgGraph/blob/main/README.md)
- [Releases](https://github.com/Evokoa/pgGraph/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/evokoa-pggraph
