dbt Hooks

Hooks are SQL statements or dbt operations that run automatically before or after a model executes. Use them to run warehouse-specific commands that dbt does not handle natively — like granting permissions, creating indexes, running VACUUM, or logging run metadata to a table.

The Four Hook Types

Hook               When It Runs
-----------        ------------
pre-hook           Before each model runs
post-hook          After each model runs
on-run-start       Once at the start of the entire dbt run
on-run-end         Once at the end of the entire dbt run

pre-hook and post-hook

These hooks attach to individual models. Define them in the model's config block or in dbt_project.yml.

Granting SELECT After a Model Runs

-- models/marts/fct_orders.sql

{{ config(
    materialized='table',
    post_hook=[
        "GRANT SELECT ON {{ this }} TO ROLE REPORTER",
        "GRANT SELECT ON {{ this }} TO ROLE BI_TOOL"
    ]
) }}

select ...

{{ this }} refers to the table just created. dbt runs the GRANT statements immediately after the CREATE TABLE finishes.

Creating an Index After a Table is Built (PostgreSQL)

{{ config(
    materialized='table',
    post_hook="CREATE INDEX IF NOT EXISTS idx_fct_orders_customer
               ON {{ this }} (customer_id)"
) }}

select ...

Logging Model Metadata

{{ config(
    post_hook="""
        INSERT INTO audit.model_run_log
        (model_name, run_at, row_count)
        SELECT
            '{{ this.name }}',
            current_timestamp,
            count(*)
        FROM {{ this }}
    """
) }}

select ...

on-run-start and on-run-end

These hooks run once per dbt run invocation — not per model. Define them in dbt_project.yml.

Creating a Schema Before Models Run

# dbt_project.yml
on-run-start:
  - "CREATE SCHEMA IF NOT EXISTS {{ target.schema }}"
  - "CREATE SCHEMA IF NOT EXISTS {{ target.schema }}_snapshots"

Logging the Run Start Time

on-run-start:
  - "INSERT INTO audit.pipeline_log (event, started_at)
     VALUES ('dbt_run_start', current_timestamp)"

on-run-end:
  - "INSERT INTO audit.pipeline_log (event, finished_at)
     VALUES ('dbt_run_end', current_timestamp)"

Granting Access to the Entire Schema After All Models Run

on-run-end:
  - "GRANT USAGE ON SCHEMA {{ target.schema }} TO ROLE REPORTER"
  - "GRANT SELECT ON ALL TABLES IN SCHEMA {{ target.schema }} TO ROLE REPORTER"

This is more efficient than granting per-model in post-hooks when you have many models in the same schema.

Applying Hooks to Multiple Models via dbt_project.yml

# dbt_project.yml
models:
  my_project:
    marts:
      +post-hook:
        - "GRANT SELECT ON {{ this }} TO ROLE REPORTER"

This post-hook runs after every model in the marts/ folder. No need to add it to each model's config block individually.

Calling Macros from Hooks

Hooks can call dbt macros for reusable logic:

-- macros/grant_access.sql
{% macro grant_access(role='REPORTER') %}
    GRANT SELECT ON {{ this }} TO ROLE {{ role }}
{% endmacro %}
-- In a model config
{{ config(
    post_hook="{{ grant_access(role='REPORTER') }}"
) }}

Hook Execution Diagram

dbt run starts
      |
      v
[on-run-start hooks]  ← run once for the whole pipeline
      |
      v
For each model (in DAG order):
      |
      ├─ [pre-hook(s)] run
      ├─ [model SQL] runs (CREATE TABLE / VIEW)
      └─ [post-hook(s)] run
      |
      v
[on-run-end hooks]    ← run once after all models complete
      |
      v
dbt run finishes

Common Hook Use Cases

Use Case                       Hook Type    Example SQL
-----------------------        ----------   -----------
Grant permissions              post-hook    GRANT SELECT ON {{ this }} TO ROLE X
Create search index            post-hook    CREATE INDEX ON {{ this }} (col)
Create schema if missing       on-run-start CREATE SCHEMA IF NOT EXISTS ...
VACUUM/ANALYZE (Postgres)      post-hook    VACUUM ANALYZE {{ this }}
Log run metadata               on-run-end   INSERT INTO audit.log ...
Cluster table (Snowflake)      post-hook    ALTER TABLE {{ this }} CLUSTER BY (date)

Debugging Hooks

Hook SQL appears in the dbt run output and in logs/dbt.log. If a hook fails, dbt reports the error with the hook SQL that caused it. Copy the SQL and run it manually in the warehouse to debug the exact error.

Leave a Comment

Your email address will not be published. Required fields are marked *