PgBeam Docs

Row-level Policies

Scope a credential to a slice of a table with a WHERE predicate. PgBeam appends the filter to every statement so an agent or analyst only ever sees its own rows.

A row-level policy attaches a WHERE predicate to a credential. PgBeam appends the predicate to every statement that touches the named table, so the credential reads and writes only the rows that match. Use it to scope one agent to a single tenant, an analyst to their region, or a support bot to a single customer's records, without changing your schema or your application.

Row filters apply to agent credentials and to human credentials. The predicate follows the credential, not the user, so the same query returns a different slice of the table depending on who runs it.

Define a row filter

A policy carries a list of row filters, one per table. Each filter is a WHERE expression evaluated against that table's columns.

pgbeam policies create tenant-42 \
  --mode read-only \
  --allow public.orders,public.invoices \
  --row-filter "public.orders=tenant_id = 42" \
  --row-filter "public.invoices=tenant_id = 42"

Open the policy profile, go to Row filters, pick a table, and write the predicate. The dashboard validates it against the table's columns before it saves.

Policy with row filters
{
  "name": "tenant-42",
  "mode": "read-only",
  "allow": ["public.orders", "public.invoices"],
  "row_filters": [
    { "table": "public.orders", "predicate": "tenant_id = 42" },
    { "table": "public.invoices", "predicate": "tenant_id = 42" }
  ]
}

How the filter is applied

PgBeam parses each statement, finds the references to a filtered table, and combines your predicate with the statement's own WHERE clause using AND. The credential cannot widen its own scope: a query that asks for tenant_id = 99 returns nothing, because the appended tenant_id = 42 rules it out.

StatementEffective query
SELECT * FROM ordersSELECT * FROM orders WHERE (tenant_id = 42)
SELECT * FROM orders WHERE total > 100SELECT * FROM orders WHERE (total > 100) AND (tenant_id = 42)
UPDATE orders SET status = 'x' (read-write)UPDATE orders SET status = 'x' WHERE (tenant_id = 42)
DELETE FROM orders WHERE id = 7 (read-write)DELETE FROM orders WHERE (id = 7) AND (tenant_id = 42)

In read-write mode the filter scopes writes too: an UPDATE or DELETE can only touch rows the credential can already see. A row outside the filter is invisible, so it cannot be modified.

Fails closed

The predicate is parsed and bound against the table's real columns when you save the policy, not at query time. A predicate that references an unknown column is rejected at save. A statement that PgBeam cannot rewrite safely (an unparseable query, a construct where the filter cannot be placed) is blocked rather than forwarded unfiltered.

Predicates run on the database

A row filter is a SQL expression that executes upstream. Keep predicates simple and indexed (tenant_id = 42, region = 'eu'). Reference only columns on the filtered table. PgBeam validates the predicate against the schema, but it does not rewrite a slow predicate into a fast one.

Combine with the rest of the policy

Row filters stack with the other policy rules. A credential can be read-only, allowlisted to two tables, masked on a column, budgeted, and row-filtered, all at once. PgBeam applies the allowlist first (can the credential touch this table at all), then the row filter (which rows), then masking (which column values leave the wire).

On this page