supabase
permdock supabase hook generate compiles the app's fromTable and fromJunction membership sources into a Supabase Custom Access Token Hook migration.
permdock supabase hook generate [--out supabase/permdock-hook.sql] [--check] [--db <url>]
[--active-from app_metadata.active_<scope>|<table>.<column>]
[--budget 1024] [--schema public] [--grants-out <file>|-]
permdock supabase inspect [--json] [--out [permdock.manifest.json]] [--check]Reads supabase.hook from permdock.config.ts and the policy's scopes, and writes one idempotent SQL migration: custom_access_token_hook(jsonb), the supabase_auth_admin grants and revokes, the permdock_authz_version table with its triggers, and the permdock_protect_managed trigger for IdP-owned rows. It prints the config.toml block (jwt_expiry = 900 and the hook uri) to add. It never grants anything to service_role, except to the PostgREST wrappers that supabase.hook.api asks for. The Supabase token hook page covers the claims and the runtime side.
| Flag | Values | Default | Effect |
|---|---|---|---|
--out | a path | supabase.hook.out, else supabase/permdock-hook.sql | Where the migration is written |
--check | flag | off | Exit 1 when the file on disk differs from what would be written; the message names the marker fields that changed (budget 2048 -> 1024) or a missing marker line |
--active-from | app_metadata.<key>, <table>.<column> | supabase.hook.activeFrom, else app_metadata.active_<first scope> | Where the active first-scope id comes from; its memberships go first and it becomes the tenant claim when the user holds one there |
--budget | a positive integer | supabase.hook.budget, else supabaseMembershipsBudget (1024) | Bytes of JSON the memberships claim may use before it is truncated and memberships_truncated: true is set |
--schema | a Postgres identifier | supabase.hook.schema, rls.schema, else public | Schema of the hook, its trigger functions and the version table |
--grants-out | a path, or - for stdout | none | Write the schema and function privileges supabase db diff drops (the supabase_auth_admin usage and execute grants, the execute revokes) to this file instead of the hook migration, for declarative schemas; --check compares it too |
--db | a Postgres URL | none | Look for the SQL helpers in this database instead of in rls.out and the migration folders; needs the optional pg peer |
supabase.hook also takes memberships (the sources, required), roles (the global roles table, false for none), attrs ({ table?, id?, columns }: server-owned columns and app_metadata.<key> entries copied into the attrs claim; user_metadata and client-writable columns are refused), claims ({ <claim>: '<schema>.<function>' }: claims other packages own, each the result of a (uuid) returns jsonb function, outside the budget), version: false (no authz_ver), api ({ schema?, prefix? }: PostgREST wrappers for subject_for, members_of and authz_version_for, granted to service_role), jwtExpiry and suspension (defaults to rls.suspension; a suspended user gets empty claims). Exit 2 on a missing supabase.hook, a source whose scope the policy does not declare, a single-scope source missing an ancestor's within column, a non-positive budget, an unsafe identifier, or an attrs entry that is user_metadata, an auth.users column, a prototype key or a duplicate, or a claims entry that names a claim PermDock or Supabase Auth writes or an unqualified function. The generated migration also refuses to install while anon or authenticated can insert or update an attrs column.
After writing, hook generate warns with PD039 when the helper schema (rls.schema, default public) has no permdock_has, or no permitted_<scope>_ids or member_<scope>_ids for a declared scope (or member_<scope>_ids_for for a scope with a membership source): it reads rls.out (else rls.sql; its helpers part when the path has {part}), the SQL files in the folder of the hook file, and the migration folders (doctor.migrations), or the database with --db. The hook writes claims only those helpers read, so run permdock rls generate first.
The migration's first line is a stable marker, -- permdock:hook v1 schema=<schema> tenant=<claim> budget=<bytes> claims=<names>, so a reader can tell which hook is installed without parsing the function. A --grants-out file starts with -- permdock:grants v1 schema=<schema>.
Reading the markers
permdock/cli exports the two readers, so another tool's doctor recognises PermDock's migrations without copying the format:
import { parseGrantsMarker, parseHookMarker } from "permdock/cli";
parseHookMarker(sql); // { version: 1, schema, tenantClaim, budget, claims } | undefined
parseGrantsMarker(sql); // { version: 1, schema } | undefinedBoth read only the first line of sql and return undefined when it is not a marker. Their results are typed SupabaseHookMarker and SupabaseGrantsMarker, also exported from permdock/cli. version is the marker's major; a reader that knows only v1 treats any other value as unknown.
inspect
permdock supabase inspect prints what the hook and the SQL helpers expect: the hook's schema and output file, the helper schema and function names, the tenant claim, the budget and the SQL it measures, every claim the hook writes with its source and whether it counts toward the budget, and whether authz_ver is on. --json prints the manifest (version: 1, schemas/supabase-manifest-v1.json), which adds the membership sources, the helpers' signatures and grants (including the _for and assignment helpers rls generate writes), the RLS mode and scope types, the custom-role, global-role, suspension and assignment-trigger settings, and the columns that decide a membership. better-supabase reads it to write Storage and Realtime policies against the same helpers and claims.
| Flag | Values | Default | Effect |
|---|---|---|---|
--json | flag | off | Print the manifest as JSON instead of text |
--out | a path, or none | permdock.manifest.json | Write the manifest as JSON to this file instead of printing it |
--check | flag | off | Exit 1 when the --out file (permdock.manifest.json without --out) is missing or its JSON differs from the manifest, naming the fields that differ; formatting is ignored |
It takes --budget, --schema and --active-from like hook generate, reads the hook's output file from supabase.hook.out, and exits 2 on the same configuration errors. Without --out or --check it prints and writes nothing. A package that reads the manifest finds it at permdock.manifest.json next to permdock.config.ts without being told the path.
permdock supabase inspect --out # in the gen script
permdock supabase inspect --check # in CILast updated on
cloud
permdock cloud push publishes the catalog (permissions, hostable flags, roles, plans and relations) to a PermDock Cloud environment so its dashboard and hosted grants follow the code.
powersync
permdock powersync generate compiles the policy's read grants into PowerSync Sync Streams, and verify checks that no stream syncs a row the policy denies.