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 changeYou 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: propertiesInstead of applying, you build:
ddn supergraph build local # local development
ddn supergraph build create # a real, immutable buildEach 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.