- Row scope — which rows do they see? Filter with a
where:clause over a secure given. - Source access — can this caller query the source at all? Gate it with
#(authorize). - Column scope — which fields are exposed? Restrict them with
accept:/except:, and gate sensitive columns with a separate#(authorize)source.
$. A secure given is one Credible fills server-side from the caller’s verified identity — their email — so a caller cannot forge it. Column scope builds on the same #(authorize) gate. If you need identity resolved from something other than email, reach out.
This is separate from discovery curation (
explores / queryableSources in publisher.json), which controls which sources are listed and queryable by name — not who may query them.Row scope: secure givens
Scope rows with awhere: clause over a secure given — a given: Credible populates from the caller’s verified identity. Givens are declared at the top of the model (not inside a source) and referenced with $. Mark a custom given #(secure) and declare it set-valued (string[]), then filter with a membership test (in) — secure givens are set-valued by design (a scalar secure given isn’t enforced).
where: compares against (here, tenant_id values). At query time Credible takes the caller’s verified email, resolves their groups, and merges every applicable row into one set: a group grant is a floor shared by all its members, and individual users can be topped up with extra values. A caller with no applicable row resolves to an empty set, which matches nothing — access fails closed.
For the example above: grant the support group ["acme"] and alice@yourco.com ["globex"], and Alice — a member of support — resolves $ALLOWED_TENANTS to ["acme", "globex"] and sees both tenants’ rows, while her teammates see only acme’s.
Referencing a custom secure given in a published model is also how the platform learns it exists — the attribute appears on the Access Control page once a model gates on it — so declaring #(secure) ALLOWED_TENANTS is only half the setup; assigning values is the other half.
$GROUPS is built-in — declare GROUPS :: string[] in the model (no #(secure) needed), and Credible fills it with the names of the groups the caller belongs to in your organization (the same groups you manage in Users & Groups), with no admin-portal assignment. The values are literally the group names, matched against your data exactly, including case — a group named West won’t match a west value. So filtering on $GROUPS means naming groups after your data values: to scope rows by region, create a group per region (west, east, …), add each user to their regions, and filter with a membership test:
Source access: #(authorize)
Gate whether a caller can query a source at all with #(authorize), a Malloy boolean expression over the model’s givens, placed above the source:. A source with no #(authorize) is unrestricted; stack multiple and they combine as OR (any true grants).
$GROUPS — including a custom secure given whose values you assign per user or group on the Access Control page. That lets you grant source access to specific individuals without hardcoding emails in the model:
["acme"] and she passes the gate; change or remove the assignment and her access follows — no republish needed.
Column scope: restricting fields
Control which fields a source exposes with Malloy’saccept: (an allowlist) or except: (a denylist), inside a source extend. These are static — they can’t read a given, so one source can’t show a column to some callers and hide it from others. (accept: and except: can’t be combined.)
Say the orders table has five columns: order_id, status, amount, customer_email, and credit_card_number. Either style hides the sensitive ones:
order_id, status, and amount — querying credit_card_number against either is a compile error, because the field doesn’t exist on the source.
To expose a column to some callers only, split into two sources and gate the full one with #(authorize):
billing group query orders_billing and see every column, including credit_card_number. Everyone else queries orders, where the sensitive columns don’t exist.
#(authorize) gates querying, not discovery — the gated source still appears in listings (name, fields, docs) to callers who can’t query it. To hide it from listings while keeping it queryable for authorized callers, curate it out of discovery with queryableSources: "all" — see discovery curation.
Malloy also has experimental
public / private / internal field modifiers, which control whether a field can be used in queries and in source extensions. Like accept:/except:, they are static — not per-caller access control. Use the source-split pattern above for identity-based column access.Have custom access control requirements? Contact us to discuss your use case.
Next Steps
Embedded Analytics
Build data applications with user-specific access
Users & Groups
Configure environment and package-level access