# postgres-meta: a REST API over the Postgres system catalog

> postgres-meta is the HTTP server Supabase uses to inspect and change a database, wrapping the Postgres system catalog in JSON endpoints for tables, columns, functions, roles, schemas, extensions, publications, and triggers. It is also a type generator, producing TypeScript, Go, Swift, and Python definitions from a live database.

**supabase/postgres-meta** — A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries

- Repository: https://github.com/supabase/postgres-meta
- Website: https://supabase.com
- Stars: 1,255 · Forks: 228
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/supabase-postgres-meta

## What the API covers, and what it does not

postgres-meta is a RESTful API for managing your Postgres: fetch tables, add roles, and run queries, and more. The README keeps a checklist of what is implemented, and it is worth reading because it is a precise statement of scope.

The schema section lists the endpoints. /query is done, with POST to execute a SQL query, POST to format a SQL query, and POST to parse a SQL query into an AST, while POST to explain a SQL query is still open. Then there is a full CRUD set for /columns, /extensions, /functions, /publications, /roles, /schemas, /tables, and /triggers, each with GET to list, POST to create, PATCH to alter, and DELETE to drop, and each annotated with the SQL it maps to, such as alter table add column or create role.

The /types resource is only partly done. GET to list types is complete, while create, alter, and drop type are still open.

The helpers section is where the interest lies, because it is the part that is most clearly a plan rather than a shipped feature. The whole section is unchecked: GET /version for the Postgres version, GET /openapi to generate an Open API document, and type generators for TypeScript, Swift as beta, and Python as beta.

So the picture is a server whose schema manipulation is mature and whose developer tooling is mid-build. That combination is exactly what you would expect from a project that started as an internal component of the Supabase platform and is being generalised.

The project is Apache-2.0 licensed according to the README, written in TypeScript, the home page is supabase.com, and the last push to the repository was on 2026-10-08. The releases are frequent, v0.100.0 from 2026-10-06, v0.99.0 from 2026-08-31, and v0.98.0 from 2026-08-14, and the version numbers crossing 100 tell you this is a component that is deployed and updated constantly.

## The reasons it exists, including the one you would not guess

The FAQ explains why the project exists, and the first answer is not the one you would expect.

It serves as a light-weight connection pooler. It also normalises the Postgres system catalog into a more readable format. While there is a lot of re-inventing right now, the server will eventually provide helpers such as type generators. And the server is multi-tenant, so it can support multiple Postgres databases from a single server.

The connection pooler point comes first, which tells you that the primary reason for this server is operational rather than developer-facing. A browser-based SQL editor and a dashboard need a single point that holds connections to the database and hands out operations, because every client holding its own pool is a problem when you have a Postgres with a low connection limit and a hundred browser tabs open. The REST API is what that point looks like from the outside.

The catalog normalisation is the second reason, and it is the one that makes the API pleasant to consume. The Postgres system catalog, in pg_class, pg_attribute, pg_proc, and the rest, is a normalised and rather awkward relational model of what a developer thinks of as tables with columns. Turning that into a JSON document per table, with the columns inlined, is the service that a dashboard wants.

The multi-tenancy point follows from the same place. One server, several databases, each request routed to the right one. That is a control plane shape, and it is why the security answer below is what it is.

## The security answer, which is the important part

The FAQ has a section on security, and the answer is two sentences that nobody should skip.

It says: none. Please don't use this as a standalone server. This should be used behind a proxy in a trusted environment, on your local machine, or using this internally with no access to the outside world.

There is no authentication, no authorisation, and no tenant isolation in the HTTP layer. The multi-tenancy mentioned earlier is a routing feature, not a security boundary, because the server will happily execute whatever SQL arrives at POST /query as the database user it is configured with. The role endpoints make it worse, since they can create and alter database roles.

That makes the deployment model the whole security model. Run it on your machine, or behind a proxy that does the authentication, in a network where the only clients are ones you control. This is a normal and reasonable design for an internal component, and it is also a design that will bite anyone who exposes it, so the README says it twice in as many words.

The practical check before you deploy it anywhere is to look at what database role the PG_META_DB_USER environment variable names, because that role's privileges are the effective privilege set of every caller of the API.

## Running it, and the environment variables

The quickstart is a set of environment variables and a binary from the releases page:

```bash
PG_META_HOST="0.0.0.0"
PG_META_PORT=8080
PG_META_DB_HOST="postgres"
PG_META_DB_NAME="postgres"
PG_META_DB_USER="postgres"
PG_META_DB_PORT=5432
PG_META_DB_PASSWORD="postgres"
```

Then you run any of the binaries in the releases. The Dockerfile shows the containerised form of the same thing, based on node:22 for the build and node:22-slim for the runtime, with the build stage running npm clean-install separately so node_modules is cached, then npm run build and npm prune --omit=dev.

Two details in the Dockerfile are the kind that come from running this in anger. The environment sets PG_META_PORT=8080 in the image, so the default is baked in rather than left to the operator. And the comment about npm run start not forwarding signals to a child process is why the CMD is node dist/server/server.js directly rather than the npm script, so that a container stop actually reaches the server rather than npm.

There is a healthcheck on the /health endpoint, with a five second interval and three retries, and it is written as a small fetch expression that throws on a non-200 status, which is a tidier way of expressing the check than shelling out to curl in an image that does not have curl.

The build script is worth noting as well, because it copies the SQL files and the format worker into the dist directory with cpy, which means the server's SQL templates and its formatting worker are shipped as assets alongside the compiled TypeScript rather than being inlined.

## The type generators, and the snapshot test workflow

The developers section describes how the project is worked on, and the type generator workflow is the most reusable part of it.

To make changes to the type generation, you run npm run gen:types:<lang> while you have npm run dev running, where <lang> is one of typescript, go, swift as beta, or python as beta. To use your own database connection string instead of the provided test database, you set PG_META_DB_URL to a postgresql URL in the environment. Each generator is a separate script that sets PG_META_GENERATE_TYPES to a language and runs the server with a ts-node ESM loader, so the generator is the server itself in a different mode rather than a separate program.

The test workflow is described in a level of detail that is unusual in a README. If you are fixing a bug, you should create a new test case. To test your changes, you add the -u flag to vitest on the test:run script, run npm run test, and then review the git diff of the snapshots. Depending on your change, you may see id fields being changed, which is expected and you are free to commit it as long as it passes CI. And do not forget to remove the -u flag when committing.

That is a snapshot-based test suite where generated output is the fixture, and the warning about id fields changing is a practical one. Generated identifiers often embed a hash of the schema, so any schema change renames them and the diff becomes noisy. The instruction to review the diff rather than accept it blindly is what keeps that useful.

The dev script is also worth reading, since it shows how the test database is managed. npm run dev traps an interrupt to run db:clean, then runs db:clean and db:run before starting the code watcher, and db:clean and db:run are docker compose commands in the test/db directory. The test script has the same shape, running the database up, the tests, and the database down again.

## Conclusion

Adopt postgres-meta if you build tooling that needs to read or change a Postgres schema over HTTP, or if you want generated types for a database you already have. Do not deploy it as a standalone public service, because the README states it has no security of its own. Verify first that the endpoints you need exist, since the progress list in the README marks the types and helpers sections as unfinished.

## FAQ

### What is postgres-meta and what is it for?

It is a RESTful API for managing a Postgres database, covering tables, columns, functions, roles, schemas, extensions, publications, and triggers, plus a query endpoint that executes, formats, and parses SQL. It also serves as a light-weight connection pooler and normalises the Postgres system catalog into a more readable format.

### Is postgres-meta safe to expose on a public network?

No. The README says the security is none, and that you should not use it as a standalone server. It should be used behind a proxy in a trusted environment, on your local machine, or internally with no access to the outside world.

### Which type generators does postgres-meta have?

TypeScript, Go, Swift as beta, and Python as beta. You run npm run gen:types:<lang> while npm run dev is running, and you can point it at your own database with PG_META_DB_URL set to a postgresql connection string.

### How do I configure postgres-meta?

Through environment variables: PG_META_HOST, PG_META_PORT, PG_META_DB_HOST, PG_META_DB_NAME, PG_META_DB_USER, PG_META_DB_PORT, and PG_META_DB_PASSWORD. Then you run one of the binaries published in the releases, or use the Docker image, which bakes in PG_META_PORT=8080.

### Which parts of the postgres-meta API are not finished?

POST to explain a SQL query is still open, as are create, alter and drop type on the types resource. The whole helpers section is unchecked, including the version endpoint, the Open API generator, and the type generators listed in the README's progress list.

## Sources

- [License: Apache-2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
- [Project website](https://supabase.com)
- [README](https://github.com/supabase/postgres-meta/blob/master/README.md)
- [Releases](https://github.com/supabase/postgres-meta/releases)
- [supabase/postgres-meta on GitHub](https://github.com/supabase/postgres-meta)

---

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