Guide
How to label dbt jobs for BigQuery cost visibility
The GCP invoice tells you what BigQuery cost, at project level. It does not tell you which dbt model, package, or team ran the query. The missing piece is labels: dbt can stamp them on every job and every table it creates.
·
Costory team
·
5 min read
Originally published 28 October 2024. Refreshed 12 November 2026.
The GCP invoice tells you what BigQuery cost, at project level. It does not tell you which dbt model, package, or team ran the query. The missing piece is labels: dbt can stamp them on every job and every table it creates. You then read INFORMATION_SCHEMA and, if you want, join those labels to the bill.
This is a how-to you run in your own dbt project. Costory can use the labels once they exist. It does not apply them for you, and it does not change your GCP account.
What BigQuery actually charges for
Two lines matter: storage and processing. Current rates live on Google Cloud's price list; they change, so this page does not quote them.
Storage is billed on the data you keep. Data modified in the last 90 days is active storage; data untouched for 90 days moves to long-term storage at a lower rate. You also choose whether to bill logical bytes (the uncompressed size you query) or physical bytes (what BigQuery stores after compression). The right choice depends on how compressible the data is and how often you query it.
Processing is billed under one of two models:
On-demand: you pay for the bytes each query processes.
Capacity (BigQuery Editions / slots): you reserve (and can autoscale) slot capacity at a fixed rate.
Until jobs and tables carry labels, both lines stay stuck at project or dataset grain. The rest of this guide is how to get to model grain.
Label the jobs dbt runs
A SQL comment in the query text is not enough. BigQuery job labels are a separate key/value map, and they are what INFORMATION_SCHEMA.JOBS_BY_PROJECT and the Job explorer actually filter on.
In dbt_project.yml, set query-comment so the comment is JSON, and turn on job-label:
Then add a macro that returns a JSON object. Keys and values must satisfy BigQuery's label rules: lowercase, no more than 63 characters, and only letters, numbers, underscores, and hyphens. Values must start with a letter or underscore. Do not put file paths, model unique IDs, or mixed-case names in a label; sanitize them first, or BigQuery will drop the label silently.
This follows the current dbt BigQuery pattern: query-comment.job-label: true plus a macro that returns JSON. Official reference: query-comment and BigQuery configurations.
Once this is in place, every dbt run, test, and snapshot should show labels on the job in the BigQuery console. If they do not appear, check the charset and length first; that is the usual failure.
Read job cost by model
Replace PROJECT_ID and the region with yours. The query only reads metadata.
On capacity pricing, total_slot_ms is the useful column (which models hold slots, including during autoscale). On on-demand pricing, total_bytes_billed is the useful one. Unlabeled jobs will land in a NULL model row; that is the gap to close, not a query bug.
The same labels also make the Job explorer usable: filter on app=dbt and a model name when a run looks expensive.
For four read-only INFORMATION_SCHEMA queries that find repeated queries, logical vs physical storage, on-demand vs slots, and forgotten tables, see BigQuery cost audit. That article does not replace this one: it does not cover dbt labels.
Label the tables dbt creates
Job labels explain processing. Storage is on the table. In a model, set labels in config():
You can also set default labels in dbt_project.yml. A label key set on the model wins over the same key in dbt_project.yml. dbt does not merge the two maps. (The original 2024 version of this article had that the wrong way around.)
Table labels show up in INFORMATION_SCHEMA.TABLE_OPTIONS. One way to unpack them:
option_value is a SQL-literal array of structs. Parse it in SQL or downstream, then join to INFORMATION_SCHEMA.TABLE_STORAGE if you want current physical and logical bytes per labeled table. Storage in the invoice is still often at dataset grain; table labels are how you allocate that line yourself.
What to do with the labels
Keep the labeling in git, next to the models. That is the source of truth. Once jobs and tables are labeled, you can:
build a scheduled query or a dashboard on the two
INFORMATION_SCHEMAviews abovejoin the same keys to your BigQuery billing export
hand the bill, plus those labels, to a tool that already reads both
Costory is one such tool. It recommends how to allocate the labeled cost; it does not edit your dbt project or your cloud resources. If you want that path after the labels exist, see the BigQuery cost by dbt model skill and the BigQuery integration.
Takeaway
Stamp dbt metadata onto BigQuery jobs with
query-commentandjob-label: true.Respect BigQuery's label rules, or the labels never appear.
Query
INFORMATION_SCHEMA.JOBS_BY_PROJECTfor processing cost by model.Set
labelson models for storage; the model config overridesdbt_project.yml.You implement the labels. Anything that reads them, including Costory, comes after.

Put this into practice with your own cost data
30-day free trial. Plans from €250/month, billed annually. We recommend Team (€600/month) once more than one team uses Costory.
First optimization recommendation
The next day
PRICE
From 250€/month, never a % of your bill
Setup
Under 30 minutes
Data residency
EU
Access
Read-only on your cloud
From 250€/month, never a % of your bill. 30 days free, no credit card.
