Importing from Hasura¶
Provisa can convert existing Hasura metadata into a Provisa config.yaml, preserving tracked tables, relationships, permissions, and remote schemas.
Interactive import (Admin → Import Hasura Config)¶
The admin surface runs the same converters, so an import needs no shell access and no config file
round-trip. Requires the org_settings capability; the import lands in the organization the
session is acting in.
- Upload. Choose a zipped Hasura v2 metadata directory, a zipped DDN project, a consolidated
metadata export (
.yaml/.json, including the{resource_version, metadata}envelope the metadata API returns), or a single.hml. Leave the format on Detect automatically unless the upload is ambiguous. - Map domains (optional). Each pair maps a v2 schema or a DDN subgraph to a Provisa domain; anything unmapped keeps its original name.
- Convert and preview. The server converts and returns counts, the conversion report (see The report), and the generated configuration. Nothing is written at this step.
- Review and edit. The configuration is editable in place — connection details, domain names, role names. What you apply is what is shown.
- Apply. Apply to this organization loads the configuration and rebuilds the organization's schemas. An import is always a merge into what the organization has.
Endpoints: POST /admin/import/hasura/preview and POST /admin/import/hasura/apply.
Hasura v2¶
Export Metadata¶
Both export layouts convert.
The CLI's metadata directory. hasura metadata export writes a directory. databases/databases.yaml lists each source with its kind and connection, and points at the source's tables and functions through !include indexes:
# metadata/databases/databases.yaml
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: HASURA_GRAPHQL_DATABASE_URL
tables: "!include default/tables/tables.yaml"
functions: "!include default/functions/functions.yaml"
The flat layout (tables.yaml and actions.yaml at the root) converts too.
The single metadata API document. The metadata API returns the whole export as one object, with the per-source configuration the directory layout has nowhere to put:
curl -X POST http://localhost:8080/v1/metadata \
-H "X-Hasura-Admin-Secret: <secret>" \
-d '{"type":"export_metadata","args":{}}' \
> metadata.json
The bare object and the {resource_version, metadata} envelope are both accepted. The admin import takes this form (see Interactive import); the command-line converter reads directories only.
Convert¶
The command-line converter reads a Hasura metadata directory (the layout hasura metadata export produces, or the flat tables.yaml / actions.yaml layout) and writes a Provisa config:
Omit -o to write the config to stdout. When something is left to redo by hand, the report prints to stderr.
Flags:
| Flag | Purpose |
|---|---|
-o, --output |
Output YAML path (default: stdout) |
--source-overrides |
YAML file with per-source connection overrides (host, port, credentials), keyed by source name |
--domain-map |
Schema-to-domain mappings as SCHEMA=DOMAIN pairs |
--auth-env-file |
.env file with auth config; converts JWT/JWK, admin secret, and claims map |
--dry-run |
Parse and validate without writing output |
The report¶
Every conversion sorts what it found into three parts. Read them in this order.
- Carried as it is. The object has a direct Provisa counterpart and converts unchanged in meaning: sources, tables, approved relationships, select permissions, cron triggers.
- Mapped to a better Provisa feature. Provisa has a feature that does the job better than the Hasura one, so the object becomes that feature. Each item says why. Event triggers, actions, remote schemas, extra root field names and roles inherited from several roles land here.
- Redo by hand. The object has no Provisa equivalent, or the export lacks what the conversion needs. Each item says what to do. These are the warnings.
The preview returns the three parts as lists. The command-line converter prints them to stderr as Carried as, Mapped to and Redo by hand, each with a count. A conversion with nothing in the third part is finished.
What Gets Converted¶
| Hasura concept | Part | Provisa result |
|---|---|---|
Source (kind, connection_info) |
carried | sources[]. A connection written out in the export converts to host, port, database and credentials. One read from an environment variable is not guessed (see Connections and domains). |
| Tracked table | carried | A registered table. Its custom name (or its select root field) becomes the table alias. |
Custom column names (column_config, or the older custom_column_names) |
carried | Column alias. |
Root field names beyond select (select_by_pk, select_aggregate, insert, ...) |
mapped | Reported by name. Provisa serves a table as one field: a by-key read is a filter on it, an aggregate its _aggregate field, a write its mutation. Check that the app's queries take that shape. |
| Object and array relationship | carried | An approved relationship (many-to-one for object, one-to-many for array) whose GraphQL field is the Hasura relationship name. See below for the cases that differ. (REQ-1680) |
| Select permission | carried | Column grants (visible_to) and a row rule. A session-variable term (X-Hasura-User-Id) becomes current_setting('provisa.hasura_user_id'), which the request binds at query time from the identity's claims or the member's attributes. A Hasura session variable keeps its own name; provisa.user_id stays the signed-in identity. (REQ-1682) |
| Row rule through a relationship | carried | An EXISTS subquery over the related table. |
| Insert, update and delete permission | carried | Write grants on the table: one per role and operation. columns become the grant's columns, filter its rows, check its check, and set its presets. |
| Inherited role of one role | carried | That role as parent_role_id. |
| Inherited role of several roles | mapped | A role holding the union of their grants, written out: a Provisa role has one parent. |
| Computed field | redo | A column of a view over the table. |
| Tracked function | carried | A command; see below. Otherwise redo. |
| Action | mapped | A command calling the action's handler. |
| Cron trigger | carried | A scheduled trigger. |
| Event trigger | mapped | A trigger on changes to the table. |
| Remote schema | mapped | A graphql_remote source whose root fields land as governed tables. (REQ-1681) |
Relationship cases:
- Collision. When the table already has a field with the relationship's name, the relationship takes the name Provisa gives it, and the report warns. Rename it on the Relationships page if the app needs another name.
- Same join twice. A second relationship declaring a join already declared merges into the first. The report says so under carried.
- Several column pairs. A Provisa relationship joins on one column pair. A relationship joining on several is warned and not created, but row rules through it are carried.
- Foreign key column only. An object relationship declared by
foreign_key_constraint_on: artist_idnames no target in the export. The converter resolves it through the inverse array relationship and warns when there is none.
Write grant details:
- An empty
filterorcheckis every row, writtenTRUE. - A
setvalue that is a session variable (X-Hasura-User-Id) becomes asessionpreset of the matching session variable (X-Hasura-User-Idbecomeshasura_user_id). Any other value becomes aliteralpreset. A preset column is not also in the grant's columns. - Hasura's implicit admin holds every permission. It becomes the role
org_admin, which holds an insert, update and delete grant over every column and every row, and is named on every imported column. (REQ-1684) - Each role with an insert, update or delete permission also receives the
writeright.
Command details:
- Tracked function. The export names the function and the roles that call it, not its arguments or what it returns. The preview reads both from the database's catalog and registers a command whose signature it found. When the source is not reached, is not PostgreSQL, or the function is missing, overloaded, or returns rows of a table the import does not register, the function goes to redo. The command-line converter never reaches a database, so every tracked function it finds goes to redo.
- Action. The command calls the action's handler as a
POST. A{{VAR}}in the handler URL is written${env:VAR}and resolved when the command is called. The command is visible to the roles the action's permissions name, or toorg_adminalone when it names none. An asynchronous action is warned: its command waits for the handler's answer. A return type whose fields the export does not carry is warned. - Event trigger. The trigger fires on every change to the table, once per batch of changes, and posts the table and the changed rows. It does not fire per row with old and new values. A Hasura trigger on fewer than all three operations is warned, since the Provisa trigger fires on all of them. Retries are not carried: a Hasura trigger with
num_retriesis warned, because a Provisa trigger calls once. Retry in the handler if a missed call matters. - Cron trigger. The cron expression, URL, payload and enabled flag carry over. Retries are not carried and are warned the same way.
A consolidated document also holds sections the converter has no target for: query_collections, allowlist, rest_endpoints, network and opentelemetry. When one is present, the report lists it under redo. The directory reader never opens their files.
Connections and domains on the import tab¶
A connection the export spells out converts as written. When the export reads a database URL from an environment variable (database_url: {from_env: HASURA_GRAPHQL_DATABASE_URL}), the variable's value is not in the export, so the conversion does not guess one. The source is registered with empty connection fields, and the report says to redo it: enter the source's host, port, database, user and password in the import's source settings and convert again. Only the fields you change travel, as source overrides. The domain rows cover every schema, subgraph and remote schema the upload carries; each is a picker over the organization's existing domains that also accepts a typed name, marked "new domain" when it matches none. Apply merges into what the organization already has. (REQ-1687)
Types come from the source at preview¶
A Hasura export names columns without types, and a tracked table with no permission names no columns. The preview runs with the source connections you supply, so it reads each reachable SQL source's information_schema.columns: every untyped column gets the source's type mapped to the IR vocabulary, and a table with no columns takes every column the source has, visible to org_admin alone, since Hasura exposed it to no other role. A source the preview cannot reach is reported as a [sources] warning and its columns stay untyped for you to finish before apply. (REQ-1691, REQ-1684)
Limitations¶
- Computed fields are redone by hand: the export names the database function but does not define it. Add it as a column of a view over the table, or compute it in the app.
- Remote schemas land as tables from the role permission SDLs. A remote schema with no permissions lands nothing, since the export carries no other statement of its shape. (REQ-1681)
- Tracked functions need the database at preview time to read their signature; offline, they are redone on the Commands page.
- Event and cron trigger retries are not carried.
Hasura DDN (v3)¶
Locate the HML project¶
The DDN converter reads the DDN project directory of .hml files directly — no supergraph build step is required. The first directory component under the project root is taken as the subgraph name; files under globals/ are assigned the globals subgraph.
Convert¶
Omit -o to write the config to stdout.
Flags:
| Flag | Purpose |
|---|---|
-o, --output |
Output YAML path (default: stdout) |
--source-overrides |
YAML file with per-source connection overrides |
--domain-map |
Subgraph-to-domain mappings as SUBGRAPH=DOMAIN pairs |
--aggregates-output |
Output path for the aggregate-expressions sidecar (default: <output>-aggregates.yaml) |
--dry-run |
Parse and validate without writing output |
AggregateExpression metadata is preserved in a sidecar *-aggregates.yaml file.
What Gets Converted¶
| DDN concept | Provisa equivalent |
|---|---|
| Subgraph model | tables[] under a source |
| Relationship | relationships[] |
| Permission rule | RLS filter |
| Command | Webhook mutation or view |
| Connector | Source entry with connection details |
Limitations¶
- Lambda connectors (TypeScript/Python functions) require manual webhook setup
- Lifecycle plugins have no direct equivalent
- DDN auth modes map to Provisa auth providers but JWT claim paths may need adjustment
After Import¶
- Review the generated
config.yamland the report — start with theRedo by handitems - Verify connection credentials. A connection the export read from an environment variable arrives empty; the report names it
- Start Provisa and confirm tables appear in the Explorer
- Run your existing GraphQL queries — the schema is compatible for common patterns
- Submit queries for approval via the Admin API or UI before enabling production governance