# freshness

### Project file

dbt\_project.yml

```yaml
sources:
  <resource-path>:
    +freshness:
      warn_after:  
        count: <positive_integer>
        period: minute | hour | day
```

### Model YAML

models/\<filename>.yml

```yaml


sources:
  - name: <source_name>
    config:
      freshness: # changed to config in v1.9
        warn_after:
          count: <positive_integer>
          period: minute | hour | day
        error_after:
          count: <positive_integer>
          period: minute | hour | day
        filter: <boolean_sql_expression>
      # changed to config in v1.10
      loaded_at_field: <column_name_or_expression>
      # or use loaded_at_query in v1.10 or higher. Should not be used if loaded_at_field is defined
      loaded_at_query: <sql_expression>

    tables:
      - name: <table_name>
        config:
          # source.table.config.freshness overrides source.config.freshness
          freshness: 
            warn_after:
              count: <positive_integer>
              period: minute | hour | day
            error_after:
              count: <positive_integer>
              period: minute | hour | day
            filter: <boolean_sql_expression>
          # changed to config in v1.10
          loaded_at_field: <column_name_or_expression>
          # or use loaded_at_query in v1.10 or higher. Should not be used if loaded_at_field is defined
          loaded_at_query: <sql_expression>

        ...
```

## Definition

A freshness block is used to define the acceptable amount of time between the most recent record, and now, for a table to be considered "fresh".

In the `freshness` block, one or both of `warn_after` and `error_after` can be provided. If neither is provided, then dbt will not calculate freshness for the tables in this source.

* `warn_after`: Duration (for example, 12 hours) after which dbt raises a warning if the most recent available data is older than this threshold.
* `error_after`: Duration (for example, 24 hours) after which dbt fails the freshness check if the most recent available data is older than this threshold.

In most cases, the `loaded_at_field` is required. Some adapters support calculating source freshness from the warehouse metadata tables and can exclude the `loaded_at_field`. (Applies to dbt v1.10 and later) Alternatively, you can define `loaded_at_query` to use custom SQL expression to calculate the timestamp.

If a source has a `freshness:` block, dbt will attempt to calculate freshness for that source:

* If `loaded_at_field` is provided, dbt will calculate freshness via a select query.
* If `loaded_at_field` is *not* provided, dbt will calculate freshness via warehouse metadata tables when possible.

(Applies to dbt v1.10 and later)

* If `loaded_at_query` is provided, dbt will calculate freshness via the provided custom SQL query.
* If `loaded_at_query` is provided, `loaded_at_field` should not be configured.

Currently, calculating freshness from warehouse metadata tables is supported on the following adapters:

* [Snowflake](../resource-configs/snowflake-configs.md)
* [Redshift](../resource-configs/redshift-configs.md)
* [BigQuery](../resource-configs/bigquery-configs.md) (Supported in [`dbt-bigquery`](https://github.com/dbt-labs/dbt-bigquery) version 1.7.3 or higher)
* [Databricks](../resource-configs/databricks-configs.md) (Supported in the dbt Fusion engine)

(Applies to dbt v1.12 and later)

Wildcard table identifiers

On BigQuery, metadata-based freshness checks are not reliable for sources defined with wildcard table identifiers (for example, `events_*`).

To prevent incorrect freshness results, enable the [`bigquery_reject_wildcard_metadata_source_freshness`](../global-configs/bigquery-changes.md#the-bigquery_reject_wildcard_metadata_source_freshness-flag) flag in your `dbt_project.yml`. When enabled, dbt raises an error if metadata-based freshness is used with a wildcard table identifier.

To calculate freshness for wildcard tables, configure [`loaded_at_field`](#loaded_at_field) to use query-based freshness checks instead.

Freshness blocks are applied hierarchically:

* A `freshness` and `loaded_at_field` property added to a source will be applied to all tables defined in that source.
* A `freshness` and `loaded_at_field` property added to a source *table* will override any properties applied to the source.

This is useful when all of the tables in a source have the same `loaded_at_field`, as is often the case.

To exclude a source from freshness calculations, explicitly set `freshness: null`.

State-aware orchestration is now dbt State

[dbt State](../../docs/deploy/dbt-state-about.md) works with all engines and environments: dbt Core, dbt platform, and Fusion

If you're using state-aware orchestration prior to June 1, 2026, you can continue using it. Your dbt State trial will be extended until the billing period begins on September 1, 2026. If your trial wasn't extended, contact your account team. To get started, refer to [Migrate from state-aware orchestration](../../docs/deploy/dbt-state-migration.md).

In state-aware orchestration, dbt uses the warehouse metadata by default to check if sources (or upstream models in the case of Mesh) are fresh. For more information on how freshness is used by state-aware orchestration, see [Advanced configurations](../../docs/deploy/state-aware-setup.md#advanced-configurations).

If you're using [dbt State](../../docs/deploy/dbt-state-about.md), `loaded_at_field` and `loaded_at_query` are also used for source freshness detection (for example, to ensure late-arriving records are detected). Refer to [Migrate from state-aware orchestration](../../docs/deploy/dbt-state-migration.md) for more details.

## loaded\_at\_field

Optional on adapters that support pulling freshness from warehouse metadata tables, required otherwise.<br /><br />A column name (or expression) that returns a timestamp indicating freshness.

Examples:

```yml
sources:
  - name: inventory_updates
    config:
      freshness:
        error_after:
          count: 24
          period: hour
      loaded_at_field: updated_at
```

If using a date field, you may have to cast it to a timestamp:

```yml
sources:
  - name: work_orders
    description: |
      Work orders from ERP. The completed_date column is stored as DATE but we need to compare it as a timestamp for freshness checks.
    config:
      freshness:
        error_after:
          count: 24
          period: hour
      loaded_at_field: "completed_date::timestamp"
```

Or, depending on your SQL variant:

```yml
sources:
  - name: purchase_orders
    description: |
      Purchase orders. The completed_date is stored as VARCHAR in 'YYYY-MM-DD' format. Use CAST for explicit conversion.
    config:
      freshness:
        error_after:
          count: 24
          period: hour
      loaded_at_field: "CAST(completed_date AS TIMESTAMP)"
```

If using a non-UTC timestamp, cast it to UTC first:

```yml
sources:
  - name: customer_transactions
    description: |
      Customer transactions recorded in Sydney local time. Converting to UTC for consistent freshness comparison across sources in different timezones.
    config:
      freshness:
        error_after:
          count: 24
          period: hour
      loaded_at_field: "convert_timezone('Australia/Sydney', 'UTC', created_at_local)"
```

(Applies to dbt v1.10 and later)

## loaded\_at\_query

Specify custom SQL to generate the `maxLoadedAt` timestamp on the source (rather than via warehouse metadata or the `loaded_at_field` config). Note that `loaded_at_query` should not be used if `loaded_at_field` is defined.

Examples:

```yaml

sources:
  - name: your_source
    config:
      freshness: # changed to config in v1.9
        error_after:
          count: 2
          period: hour
      loaded_at_query: |
        select max(_sdc_batched_at) from (
        select * from {{ this }}
        where _sdc_batched_at > dateadd(day, -7, current_date)
        qualify count(*) over (partition by _sdc_batched_at::date) > 2000
        )
```

```yaml

sources: 
  - name: ecom
    schema: raw
    description: E-commerce data for the Jaffle Shop
    config: 
      freshness: 
        warn_after:
          count: 24
          period: hour
    tables:
      - name: raw_orders
        description: One record per order
        config:
          loaded_at_query: "select {{ current_timestamp() }}"
...
```

Should not be configured if `loaded_at_field` is also configured, but if it is, dbt will use which ever value is closest to the table.

[Filter](#filter) won't work for `loaded_at_query`.

## count

(Required)

A positive integer for the number of periods where a data source is still considered "fresh".

## period

(Required)

The time period used in the freshness calculation. One of `minute`, `hour` or `day`

## filter

(optional)

Add a where clause to the query run by `dbt source freshness` in order to limit data scanned.

This filter *only* applies to dbt's source freshness queries - it will not impact other uses of the source table.

This is particularly useful if:

* You are using BigQuery and your source tables are [partitioned tables](https://cloud.google.com/bigquery/docs/partitioned-tables)
* You are using Snowflake, Databricks, or Spark with large tables, and this results in a performance benefit

## Examples

### Complete example

models/\<filename>.yml

```yaml


sources:
  - name: jaffle_shop
    database: raw
    config: 
      # changed to config in v1.9
      freshness: # default freshness
        warn_after: {count: 12, period: hour}
        error_after: {count: 24, period: hour}

      loaded_at_field: _etl_loaded_at

    tables:
      - name: customers # this will use the freshness defined above

      - name: orders
        config:
          freshness: # make this a little more strict
            warn_after: {count: 6, period: hour}
            error_after: {count: 12, period: hour}
            # Apply a where clause in the freshness query
            filter: datediff('day', _etl_loaded_at, current_timestamp) < 2


      - name: product_skus
        config:
          freshness: # do not check freshness for this table
```

When running `dbt source freshness`, the following query will be run:

### Compiled SQL

```sql
select
  max(_etl_loaded_at) as max_loaded_at,
  convert_timezone('UTC', current_timestamp()) as snapshotted_at
from raw.jaffle_shop.orders

where datediff('day', _etl_loaded_at, current_timestamp) < 2
```

### Jinja SQL

```sql
select
  max({{ loaded_at_field }}) as max_loaded_at,
  {{ current_timestamp() }} as snapshotted_at
from {{ source }}
{% if filter %}
where {{ filter }}
{% endif %}
```

*[Source code](https://github.com/dbt-labs/dbt-core/blob/HEAD/core/dbt/include/global_project/macros/adapters/common.sql#L262)*

## Was this page helpful?

YesNo

[Privacy policy](https://www.getdbt.com/cloud/privacy-policy)[Create a GitHub issue](https://github.com/dbt-labs/docs.getdbt.com/issues)

This site is protected by reCAPTCHA and the Google [Privacy Policy](https://policies.google.com/privacy) and [Terms of Service](https://policies.google.com/terms) apply.
