CertSafari
    DBT Analytics Engineering· Lessons

    Domain 1 · Lesson 9/31

    Granting access to dbt models with the grants config

    Providing access to users to models with the "grants" config

    13 min read
    3.23% of exam
    5 sources
    Published 12 Oct 2026
    Docs as of 12 Oct 2026

    What you will be able to do

    • Configure grants on models, seeds and snapshots in dbt_project.yml, YAML property files and config() blocks
    • Predict which grant and revoke statements dbt runs when a resource builds
    • Choose between clobbering and adding grantees with the + privilege prefix
    • Tell apart revoking every grant from no longer having dbt manage grants
    • Adapt privilege and grantee names to BigQuery, Redshift, Databricks and Snowflake, and know when to use hooks instead

    Key concept

    Grants as declared state — The grants config states which recipients should hold which privileges on a model, seed or snapshot. Each time that resource builds, dbt runs whatever grant and revoke statements are needed so the database matches that state exactly.

    1.What the grants config controls and where it is set

    Database permissions decide who can read, insert into or drop a table or view. A finished mart is only useful once the BI tool's account is allowed to select from it. The grants config brings those permissions into the dbt project, so dbt applies them at build time, as part of creating the object, instead of leaving them as a separate admin task after the run.

    A grant has two parts. The privilege is the action being allowed, such as select. The grantees are the recipients. Depending on the platform, a grantee can be a user, a group, a Snowflake role or a GCP service account. In the config, each privilege is a key and its grantees are the value. The value can be a single name or a list.

    Per-model grants in a YAML property file: two grantees receive select on specific_modelyaml
    models:
      - name: specific_model
        config:
          grants:
            select: ['reporter', 'bi']

    Grants work on three resource types: models, seeds and snapshots. Each type can be configured in three places, and the layers work together. Defaults that cover the whole project, a package or a subfolder go in dbt_project.yml. Resource-specific grants go in a YAML config: block, as above, or in a {{ config() }} call inside the .sql file. Snapshots follow the same pattern, using a snapshots property file or the snapshot's own config(). When more than one layer applies, the usual [config precedence](lesson:1.6) decides, and the most specific setting wins. Grants are also recorded in the project's metadata, including the manifest artifact.

    Checkpoint 1 of 8· Check yourself

    A team wants every model in the project to be readable by the reporter role, and one seed also readable by bi. Which setup does the grants config support?

    Sources123

    2.What dbt runs when a resource builds

    On the first build, dbt creates the object and then grants. Take an incremental model that grants select to two recipients:

    An incremental model granting select to bi_user and reportersql
    {{ config(materialized = 'incremental', grants = {
        'select': ['bi_user', 'reporter']
    }) }}

    The first run creates the table and then issues something like grant select on schema_name.incremental_model to bi_user, reporter;. Later runs behave differently. The table already exists, so dbt first runs database-specific SQL to list the grants it already has. It then compares that list with the config and sends only the grant or revoke statements that close the gap. If someone grants select to an extra user by hand between runs, that user is not in the config, so the next build revokes the grant.

    The exact statements depend on the adapter and on whether dbt is replacing the object or updating one that already exists. To see what actually ran, check the debug logs. They record every grant and revoke statement dbt issued.

    Checkpoint 2 of 8· Put it in order

    Put these steps in the order they happen for the incremental model above, across its first and second runs

    1. 1.On the next run, dbt uses database-specific SQL to show the grants already on incremental_model
    2. 2.dbt creates the incremental_model table
    3. 3.dbt runs a statement like grant select on schema_name.incremental_model to bi_user, reporter
    4. 4.dbt issues only the grant or revoke statements needed to match the configured grants

    Sources1

    3.Clobbering versus adding grantees with the + prefix

    Project-level default grants. The + before grants is required hereyaml
    models:
      +grants:  # In this case the + is not optional, you must include it for your project to parse.
        select: ['user_a', 'user_b']

    By default, a more specific grant list replaces a less specific one. dbt calls this "merge and clobber", and it settles the merge at parse time, one privilege at a time. With the project file above and a model config() that grants select to ['user_c'], the database ends up with exactly one grantee, user_c, and the logs show exactly one grant statement. An analyst who adds one person to a single model can therefore remove everyone the project default had granted.

    To add to the inherited list instead, prefix the privilege name with +. Writing {{ config(grants = {'+select': ['user_c']}) }} grants select to user_a, user_b and user_c. The prefix works per privilege. If the same config also grants insert without a +, the insert list still clobbers. grants is currently the only config that supports this merge-control +.

    Grant values can also use Jinja, like any other config. A common pattern switches grantees on target.name, so production and development builds grant to different people:

    Different select grantees in prod and devyaml
    models:
      +grants:
        select: "{{ ['user_a', 'user_b'] if target.name == 'prod' else ['user_c'] }}"

    Checkpoint 3 of 8· Fill the gap

    The project grants select to user_a and user_b. Which privilege key makes this model config keep both of them and also grant to user_c?

    {{ config(grants = {' ? ': ['user_c']}) }}

    Checkpoint 4 of 8· Exam question

    A dbt project sets a project-level default in `dbt_project.yml`: ```yaml models: +grants: select: ['analyst_role', 'bi_role'] ``` A specific model's SQL file then adds: ```sql {{ config(grants = {'select': ['reporting_role']}) }} ``` After the next run, which grantees actually hold the `select` privilege on that model?

    Sources14

    4.Revoking grants versus no longer managing them

    dbt only touches grants on a node, including revoking them, when that node has a grants config attached. This rule decides what happens when someone edits or deletes a grants section. Start from a project that grants select to ['user_a', 'user_b'] and consider three edits.

    Three edits to a project that grants select to user_a and user_b
    Edit in dbt_project.ymlWhat dbt does on the next build
    select: ['user_b']Revokes select from user_a. user_b keeps it
    select: []Revokes select from every grantee
    Delete the whole +grants sectionStops managing grants and changes nothing, so user_a and user_b keep select

    The third row catches people. Deleting the config looks like "remove the access", but dbt reads it as "stop managing access", so existing grants stay on the table. To lock a model down, keep the privilege key and give it an empty list.

    Checkpoint 5 of 8· Check yourself

    A model containing PII must stop being readable by the analysts who currently hold select through the project's grants config. Which change makes dbt remove that access on the next build?

    Checkpoint 6 of 8· Exam question

    The project's `dbt_project.yml` grants `select` to `finance_reporting` at the project level. One model needs an additional grantee, `finance_audit`, without losing `finance_reporting`, and without changing the merge-and-clobber default anywhere else in the project. Which config change on that model achieves this?

    Sources1

    5.Platform-specific names, and when hooks take over

    select and a plain user name are common conventions, but every platform names privileges and grantees in its own way, and dbt passes your names through as written. On BigQuery, privileges are IAM roles, such as roles/bigquery.dataViewer. Grantees carry a type prefix, so a person is written as user:someone@yourcompany.com.

    A BigQuery grant: the privilege is an IAM role and the grantee carries the user: prefixsql
    {{ config(grants = {'roles/bigquery.dataViewer': ['user:someone@yourcompany.com']}) }}
    Adapter differences that change how grants are written or whether they work
    PlatformWhat to know
    BigQueryPrivileges are roles like roles/bigquery.dataViewer. Grantees need a prefix such as user:. grants is separate from grant_access_to, which sets up authorized views
    RedshiftPrefix group: or role: for groups and roles. Unprefixed names are users
    DatabricksGrants are enabled automatically on SQL endpoints. Interactive clusters need table access control enabled. READ_METADATA and USAGE need post-hooks
    Snowflakecopy_grants is taken into account. Only Snowflake roles, not database roles, are fully supported
    OSS Apache Spark / Delta LakeGrants are not supported
    Redshift grantees: unprefixed, user:, group: and role: names togetheryaml
    models:
      +grants:
        select: ["user1", "user:user2", "group:analysts", "role:reporter"]

    A hook is a SQL statement attached to a resource through its pre_hook or post_hook config, which dbt runs before or after that resource builds. A hand-written grant statement in a hook is the alternative to the grants config. Use resource configs for grants wherever possible, and switch to hooks only when the config cannot do the job. The cases that call for hooks are:

    - grants on objects other than tables and views - row- or column-level access, masking policies, or future grants - platform permission features dbt has no config for, or grant logic more complex than the built-in config allows

    Model access is a separate setting. access: public controls which other projects may ref a model. It grants nothing in the warehouse. A public model still needs a grants config, or some other permission mechanism, before anyone outside dbt can query it.

    Checkpoint 7 of 8· Match them up

    Match each platform to how grants behave there

    Tap a term, then the definition that fits it.

    Checkpoint 8 of 8· Exam question

    A team migrating a Snowflake-based dbt project to BigQuery reuses their old `grants` config and finds it rejected at parse time. Which changes are required to make the config valid for the BigQuery adapter? (Select all that apply)(Select 2)

    Sources125

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.Deleting a model's grants config makes dbt revoke the grants it had applied.Why is that wrong?

      When there is no grants config, dbt stops managing grants and leaves the existing ones in place. Only an empty grantee list revokes them.

      Covered in Revoking grants versus no longer managing them

    2. 2.A model-level grants list adds its grantees to the ones set in dbt_project.yml.Why is that wrong?

      By default, the more specific list replaces the less specific one for that privilege. Grantees are added only when the privilege name has a + prefix, such as '+select'.

      Covered in Clobbering versus adding grantees with the + prefix

    3. 3.The + in '+select' inside a model's config() is the same + used before config keys in dbt_project.yml.Why is that wrong?

      In dbt_project.yml, + marks a config key. In a privilege name, + switches merging from clobber to add. They are two different features.

      Covered in Clobbering versus adding grantees with the + prefix

    4. 4.Setting access: public on a model grants select on it to warehouse users.Why is that wrong?

      Model access only governs ref from other projects. Database permissions still come from grants or another mechanism.

      Covered in Platform-specific names, and when hooks take over

    5. 5.The grants config can set future grants and column-level access.Why is that wrong?

      Future grants, row- and column-level access and masking policies are outside the built-in config, so they need grant statements run through hooks.

      Covered in Platform-specific names, and when hooks take over

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “define grants as resource configs on each model, seed, or snapshot”
      ↩︎ What the grants config controls and where it is set
      “Privilege: A right to perform a specific action or set of actions on an object in the database”
      ↩︎ What the grants config controls and where it is set
      “Like all configurations, grants will be included in dbt project metadata, including the manifest artifact.”
      ↩︎ What the grants config controls and where it is set
      “When dbt runs this model for the first time, it will create the table, and then run code like:”
      ↩︎ What dbt runs when a resource builds
      “You can always check the debug logs for the full set of grant and revoke statements that dbt runs.”
      ↩︎ What dbt runs when a resource builds
      “dbt's default behavior replaces the less-specific set of grantees with the more-specific set of grantees”
      ↩︎ Clobbering versus adding grantees with the + prefix
      “Like any other config, you can use Jinja to vary the grants in different contexts.”
      ↩︎ Clobbering versus adding grantees with the + prefix
      “dbt only modifies grants on a node (including revocation) when a grants configuration is attached to that node.”
      ↩︎ Revoking grants versus no longer managing them
      “To have dbt revoke all existing grants from a node, provide an empty list of grantees.”
      ↩︎ Revoking grants versus no longer managing them
      “On BigQuery, "privileges" are called "roles," and they take the form roles/service.roleName.”
      ↩︎ Platform-specific names, and when hooks take over
      “OSS Apache Spark / Delta Lake do not support grants.”
      ↩︎ Platform-specific names, and when hooks take over
      “Granting to / revoking from is only fully supported for Snowflake roles (not database roles).”
      ↩︎ Platform-specific names, and when hooks take over
      “You should define grants as resource configs whenever possible, but you might occasionally need to write grants statements manually and run them using hooks.”
      ↩︎ Platform-specific names, and when hooks take over
      “dbt ensures that the grants on its view or table match exactly the grants you have configured”
      ↩︎ Key concept
      “If you delete the entire +grants section, dbt assumes you no longer want it to manage grants and doesn't change anything.”
      ↩︎ Exam trap 1
      “This will only take effect for privileges which include the + prefix.”
      ↩︎ Exam trap 2
      “This use of +, controlling clobber vs. add merge behavior, is distinct from the use of + in dbt_project.yml”
      ↩︎ Exam trap 3
      “Create more granular row- and column-level access, use masking policies, or apply future grants.”
      ↩︎ Exam trap 5
      “dbt will use database-specific SQL to show the grants already on incremental_model, and then determine if any revoke or grant statements are needed”
      ↩︎ Checkpoint
      “Use the group: or role: prefix in grantee names to grant to groups or roles. Unprefixed names are treated as users.”
      ↩︎ Checkpoint
    2. 2.
      “The most specific configuration always takes precedence.”
      ↩︎ What the grants config controls and where it is set
      “post_hook: <sql-statement> | [<sql-statement>]”
      ↩︎ Platform-specific names, and when hooks take over
    3. 4.
      “Currently, the only config which supports + for controlling config merge behavior is grants.”
      ↩︎ Clobbering versus adding grantees with the + prefix
    4. 5.
      “does not trigger dbt to automagically grant select on that model to every user or role in your data platform”
      ↩︎ Platform-specific names, and when hooks take over
      “does not trigger dbt to automagically grant select on that model to every user or role in your data platform”
      ↩︎ Exam trap 4

    Ready to test yourself?

    Practise the 11 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.