Hasura – Metadata Is the Source of Truth

June 30, 20264 min readUpdated 8/21/2026

Hasura’s schema is not in your database and not in your application code. It lives in metadata, and understanding that one fact explains most of the product — including why two developers clicking in a console will eventually ship different APIs.

What metadata actually is

Your database has tables, columns and foreign keys. Metadata is everything Hasura layers on top: which tables are exposed, what they are called in GraphQL, which relationships exist, which roles can see which rows and columns, what Actions and triggers are configured.

Nothing you do in the console changes your database. Tracking a table adds a metadata entry. Adding a permission adds a metadata entry. Drop the metadata and your data is untouched — you just have no API.

You can read all of it:

curl -s -X POST http://localhost:8081/v1/metadata \
  -H 'x-hasura-admin-secret: stayhub-admin-secret' \
  -d '{"type":"export_metadata","args":{}}'

What comes back is one JSON document describing the whole API. Trimmed to a single table:

{
  "sources": [
    {
      "name": "default",
      "kind": "postgres",
      "tables": [
        {
          "table": { "schema": "public", "name": "properties" },
          "object_relationships": [ { "name": "host" } ],
          "array_relationships": [ { "name": "bookings" }, { "name": "images" } ],
          "select_permissions": [
            { "role": "anonymous", "permission": { "columns": ["title", "city"], "filter": {} } }
          ]
        }
      ]
    }
  ]
}

That document is your API. It is the thing worth version-controlling.

The console is a trap at team size

Clicking in the console is the right way to learn and a bad way to run a project. It writes directly to the running engine’s metadata, which means the change exists only on that engine until somebody exports it. Two developers each add a permission, each exports, and one silently overwrites the other.

The fix is to treat metadata as source. Two ways to do it.

The Hasura CLI

The official route keeps metadata as a tree of YAML files:

hasura metadata export      # engine  -> files
hasura metadata apply       # files   -> engine
hasura metadata diff        # what differs
hasura metadata reload      # re-read after a schema change

You get metadata/databases/, metadata/actions.yaml and friends. Commit them, review them in pull requests, apply them in CI.

Metadata as a program

The demo app this track uses does something different: its metadata is a Python module that builds the document and posts it to the metadata API. It needs no extra binary installed, and it means the interesting part — permission rules — can be written once and reused across roles instead of copy-pasted per table.

PUBLISHED_ONLY = {"_and": [{"status": {"_eq": "PUBLISHED"}}, {"deleted": {"_eq": False}}]}
NOTHING_HIDDEN = {}

Then apply it in one call:

curl -s -X POST http://localhost:8081/v1/metadata \
  -H 'x-hasura-admin-secret: stayhub-admin-secret' \
  -d '{"type":"replace_metadata","args":{ ... }}'

replace_metadata is exactly what it says. It does not merge — whatever is not in the document you send stops existing. That is the property you want from a source of truth, and it is also how people lose an Action they added by hand in the console last week.

Inconsistent metadata

Metadata can reference things that are no longer there. Drop a column that a permission lists and the engine starts up with that object marked inconsistent.

curl -s -X POST http://localhost:8081/v1/metadata \
  -H 'x-hasura-admin-secret: stayhub-admin-secret' \
  -d '{"type":"get_inconsistent_metadata","args":{}}'

This is the first thing to check when a field vanishes from your schema after a deploy. The engine does not refuse to start; it drops the broken object and serves the rest, which is helpful in production and confusing if you do not know to look.

Metadata in v3 (DDN)

On the v3 sections. Everything marked v3 (DDN) is taken from the official Hasura DDN documentation as read on 2026-08-21 and was not run locally — it shows configuration, never claimed output. The v2 material was executed against a running engine.

v3 takes the same idea considerably further. Metadata is no longer a live document you mutate through an API — it is a set of .hml files you edit and compile. HML is Hasura Metadata Language, an extension of YAML.

kind: Model
version: v1
definition:
  name: Properties
  objectType: Properties
  source:
    dataConnectorName: my_connector
    collection: properties
  graphql:
    selectMany:
      queryRootField: properties

Instead of applying, you build:

ddn supergraph build local        # local development
ddn supergraph build create       # a real, immutable build

Each build is immutable and gets its own unique GraphQL endpoint, so you can test one before promoting it. The whole class of “somebody applied metadata to production by accident” problems goes away, because applying is no longer the operation.

The other change is division. In v2 metadata is one document for the whole engine. In v3 it is split across subgraphs — self-contained domains with their own metadata, permissions and repository — composed into a supergraph. Global concerns like AuthConfig and GraphqlConfig live in a globals subgraph.

Next

Metadata is the how. Lesson 4 covers what it buys you: the query language you get for free.