dbt Projects
Every way to sync your dbt project into Qyra, and how to connect multiple dbt sources
Qyra supports dbt v1.4.0 and above. If you are using an older version of dbt, you will need to upgrade to sync your project to Qyra.
Syncing your dbt project to Qyra
There are five ways to sync changes from dbt into Qyra. They differ in where the code comes from, where compilation happens, which profiles.yml / target governs the result, and whether they respect the project's UI Connection Settings.
For ready-to-use workflow templates, see the qyra/cli-actions repo.
Sync mechanisms
Decision matrix
#1 qyra deploy — local CLI | #2 qyra deploy — CI | #3 "Refresh dbt" — UI button | #4 qyra refresh — CI | #5 POST /refresh API — CI | |
|---|---|---|---|---|---|
| Automated? | No | Yes | No | Yes | Yes |
| Pushes uncommitted local changes? | Yes | No | No | No | No |
| Needs dbt + warehouse creds in CI? | N/A | Yes | No | No | No |
| Needs Qyra CLI installed? | Yes | Yes | No | Yes | No |
| Respects UI Target name? | No | No | Yes | Yes | Yes |
| Works without a remote git connection? | Yes | Yes | No | No | No |
| Catches breaking changes pre-merge? | With --select + validate | If used in a PR workflow | No | No | No |
| Best for | Initial setup, debugging | PR previews; CI deploys with no remote git connection | Ad-hoc UI refresh | Post-merge sync (recommended) | Post-merge sync, minimal-CI shops |
Recommended setups
Most teams (Qyra connected to GitHub, GitLab, Azure DevOps, Bitbucket, or dbt Cloud):
- PR workflow →
qyra start-preview+qyra validate(mechanism #2 withstart-preview) - Post-merge to
main→qyra refresh(#4) or the refresh API (#5) - Manual UI refresh (#3) available for ad-hoc syncs
Teams without a remote git connection (project loaded via local CLI only):
- Production sync →
qyra deployin CI (#2) - Previews →
qyra start-previewin CI
Teams just getting started:
- Local
qyra deploy(#1) for first-time setup, then graduate to one of the automated patterns above as soon as the project is stable.
Not sure whether to use qyra deploy or qyra refresh? Read about the differences between the two commands in the CLI reference.
Things to know
qyra deploy does not use the UI Target name setting. It uses the local profiles.yml default, or whatever --target overrides it with — only mechanisms #3, #4, and #5 respect the UI Target name.
qyra refresh requires a remote git connection. It fails with a ParameterError if the project's dbtConnection.type is NONE.
Refresh is async. Both #4 and #5 return a jobUuid and the work continues server-side. If your CI needs to know the refresh succeeded, poll GET /api/v1/jobs/{jobUuid} before exiting the workflow.
Never commit profiles.yml with credentials. For mechanism #2, use either a secret-written-to-file approach or an env_var() template — but never check in plaintext credentials.
Mechanisms #4 and #5 are underused. Most CI examples lead with qyra deploy, which is the heaviest option in CI because it duplicates dbt and warehouse credentials into the runner. The refresh-based patterns are simpler and often a better fit when the project is git-connected.
Projects with multiple dbt sources must not use qyra deploy. Mechanisms #1 and #2 replace the project's Explores with only the local repo's models. Use #3, #4, or #5 instead.
How to trigger each sync
Click "Refresh dbt" in Qyra
The button can be found on the Query from tables page.

If you're using a git connection (like GitHub, GitLab, or Bitbucket), you'll need to push + merge your changes to the branch that your Qyra project is connected to before you press Refresh dbt.
If you've made any changes to the underlying data (for example, adding a new column in your model.sql file or changing the SQL logic of a dimension), then you need to run dbt run -m yourmodel before you click Refresh dbt in Qyra.
Push code from the CLI
If you're using the Qyra CLI, you can use qyra deploy to deploy your changes to Qyra. Read more about how to use qyra deploy.
We don't recommend using qyra deploy from your local environment as the primary way you update Qyra since small mistakes can lead to production issues.
Set up continuous deployment
Read how to do that and check out our example workflow files.
Connecting multiple dbt sources
Availability. On Qyra Cloud, multiple dbt sources is enabled by default, so the dbt sources panel is already visible in Project Settings. On self-hosted deployments, add multi-dbt-sources to your QYRA_ENABLE_FEATURE_FLAGS environment variable (comma-separated, so append it to any flags you already have set) on Qyra v0.3275.0 or later.
A single Qyra project can connect to more than one dbt project. Each connected dbt project is called a dbt source:
- The dbt connection you set up when you first created the Qyra project is the primary source.
- Any dbt projects you add on top of it are additional sources.
All sources are compiled together into one set of Explores, so tables from different dbt repos appear side-by-side in the same Qyra project and can reference each other via ref() and joins.
When to use this
- A monorepo you've split into multiple dbt projects and want to expose as one analytics surface.
- Multiple teams that each own their own dbt project, but share the same warehouse and want a single place to explore the data.
- Shared/common dbt packages you want to pull into a project alongside team-specific models.
All sources share the Qyra project's single warehouse connection. Multiple dbt repos, one database.
Adding and managing sources
Go to Project settings → dbt connection. When the feature is enabled for your org, you'll see a dbt sources panel that lists the primary source plus any additional ones. Each row shows the source's display name and its git identity (repository · branch · subfolder), or the connection type for non-git sources.
From the panel you can:
- Add an additional source: connect a GitHub repository, give the source a display name, and fill in its repo/branch/subfolder/credentials and any environment variables. Environment variables are validated when the source is saved. Other connection types aren't supported for additional sources.
- Edit an additional source: change its display name, connection details, or environment variables.
- Remove an additional source: additional sources can be removed at any time.
- Edit the primary source: the primary source is edited via the normal dbt connection form. It cannot be removed.
If a source's saved credentials can't be loaded, its row in the sources panel shows a warning icon. Other sources keep working normally. Only operations that need that specific source's connection will fail.
Each additional source has:
- A display name: how it appears in the sources panel and in collision errors.
- Its own dbt connection: GitHub only. The primary source can use any of Qyra's supported connection types (GitHub, GitLab, Azure DevOps, Bitbucket, dbt Cloud), but additional sources are limited to GitHub, each with its own repo, branch, subfolder, credentials, and environment variables.
- A precedence: the order in which sources are merged. Collisions fail the deploy rather than being resolved by precedence (see below). Precedence determines which source is reported as holding the existing definition and which as the duplicate.
How compilation and merging works
On refresh, Qyra compiles every source and merges the resulting manifests into a single set of Explores.
Precedence. Sources are ordered by precedence. The primary source is always 0 (highest priority). Additional sources are appended after it in the order they were added. Precedence orders the merge for ref() and lookup resolution.
Name collisions. If the same model name exists in more than one source, compilation fails with an error that names each collision and which sources define it (in the form <section> "<key>" is defined in both "<source A>" and "<source B>", listing up to 10 entries and ending with "Rename or remove the duplicate(s) before deploying."). Rename or remove the duplicates in one of the sources before deploying. Only identical dbt built-in macros and docs are merged silently.
The CLI's --combine-manifest flag is a different mechanism. It merges an external manifest into the manifest the CLI just generated, and unlike the additional-sources merge it does not fail on collisions — see qyra deploy in the CLI reference.
What's merged vs. deduplicated.
- Models: must be uniquely named across sources — a name collision fails the deploy. Once unique, models can
ref()each other and be joined across sources. - Metrics, docs, dbt sources, macros: unioned across all repos.
- Identical dbt built-in macros and docs: merged silently across repos and never reported as collisions.
Environment variables are scoped to their source's connection and validated when that source is saved.
How refreshes work
Every refresh — sync mechanisms 3, 4, and 5, plus Test & deploy in Project Settings — re-fetches and compiles all sources and merges them into one Explore set. If any single source fails (clone, manifest parsing, credentials, model-name collision), the whole refresh fails with an error naming that source. There are no partial deploys.
Do not use qyra deploy against a multi-source project. The CLI compiles the local repo and writes those Explores directly, without merging in the other sources — a CLI deploy will replace the project's Explores with only the local repo's models until the next server-side refresh. Teams using multiple dbt sources should sync via mechanism #3, #4, or #5, and must not run qyra deploy in CI against a multi-source project.
Preview projects
A preview of a multi-source project contains models from every source, not just the repo you are working in. How that happens depends on where the preview is created.
From the Qyra UI. The preview inherits the parent's additional sources, then compiles all of them server-side and stores the merged result. Branch and manifest overrides you set when creating the preview apply to the primary source only; additional sources are always compiled from the branch saved on their own connection.
From the CLI (qyra preview and qyra start-preview). The CLI compiles your local repo, then fetches the parent project's merged manifest from Qyra and combines the two before uploading. Your locally compiled models win on conflicts, so you get your working changes on top of everything else in the project. This is automatic — you don't need to pass --combine-manifest.
The CLI's automatic combine is fail-open. If the parent's merged manifest can't be fetched, the CLI prints a warning and continues with your local models alone, which produces a preview missing the other sources' models rather than an error. Read the CLI output if a preview looks short of models. The most common causes are:
- The parent project has not completed a server-side refresh since additional sources were added, so it has no merged manifest yet. Run Test & deploy on the parent first.
- Your local repo isn't recognised as one of the parent's sources, in which case the CLI says
the local dbt project is not a source of this Qyra project. - You passed
--no-combine, which skips the fetch.
Passing --combine-manifest <path-or-url> explicitly overrides the automatic fetch, and is still the right option when you want a specific manifest rather than the parent's. It takes a single file, so in a project with more than two sources that file must already contain the full merged set.
Write-back behavior
Qyra has two write-back paths, and they behave differently in a multi-source project.
AI agent write-back
Fully supported across all git-backed sources. AI agents that open PRs with dbt changes work in a multi-source project. Before making changes, the agent resolves which source to target, in this order:
- A source you explicitly specified.
- The source a resumed thread is already bound to.
- The only source, if the project has just one.
- A source named in your prompt.
If it can't decide, it doesn't guess. It asks you to pick from the candidate sources. The chosen source is remembered for the whole thread, so follow-up messages keep editing the same repo and updating the same PR.
Only git-backed sources (GitHub, GitLab, and other git connection types) can be targeted by agent write-back. If the primary source is not git-backed, the agent will skip it in favor of git-backed additional sources.
Explore write-back
Supported for the primary source only. Writing custom metrics and dimensions back as a PR from the Explore view always opens the PR against the primary source's repo. This means:
- For models that come from the primary source, Explore write-back works normally.
- For models that come from an additional source, Explore write-back will fail because the model's YAML file isn't in the primary repo.
To edit models in additional sources, use AI agent write-back or edit the source's dbt project directly.
dbt project settings
For more information about dbt connection types (Github, Gitlab, Bitbucket, etc.) and the fields required for each type, read the dbt project section in our connection guide.
Below are details about the universal fields for all connected dbt projects.
dbt selector
You can filter out models in your dbt project that you don't want to see in Qyra. This is useful if you have a large dbt project and you want to speed up the sync process. Unlike Tables configuration, this selector is applied to the dbt models, so it will skip the entire compilation process for the models that you don't want to see in Qyra.
To do this, you can add a dbt selector to your project settings. This is a JSON object that contains the models you want to include in Qyra.
For example, if you only want to include the my_model and all models with the qyra tag in Qyra, you can add the following to your dbt project settings:
my_model tag:qyraWe support all dbt selectors. Read more about selectors in the dbt docs.
dbt targets
What is a dbt target?
A dbt target is an environment configuration in your dbt project that defines where and how dbt should run. In dbt Core, targets are defined in your profiles.yml file and typically include settings like:
- Database connection details
- Schema/dataset names
- Warehouse specifications
- Other environment-specific configurations
Most teams have multiple targets for different environments:
dev- for development workprod- for production deploymentsstaging- for testing before production
Each target can have different connection settings which are stored in your profiles.yml file.
The example profiles.yml file below shows different connection settings for the dev and prod targets. Note that on line 3 target: dev indicates that the dev target is the default target for this project.
# example profiles.yml file
jaffle_shop:
target: dev
outputs:
dev:
type: postgres
host: localhost
user: alice
password: <password>
port: 5432
dbname: jaffle_shop
schema: dbt_alice
threads: 4
prod: # additional prod target
type: postgres
host: prod.db.example.com
user: alice
password: <prod_password>
port: 5432
dbname: jaffle_shop
schema: analytics
threads: 8Configuring your target in Qyra
Target contains information about your dbt connection to your warehouse.
By default, we set this to be the same value as you have as the default in your profiles.yml file when you run qyra deploy (if that's how you created or recently deployed your project). If you want to update this, you can enter the target of your choice in the project settings (for example prod or analytics).
Read more about dbt targets in the dbt docs.
How targets work with dbt macros
When Qyra connects to your dbt project, it compiles a profiles.yml file based on the connection settings you configure in the Qyra UI or the connection settings from your profiles.yml if you deployed your project from the CLI using qyra deploy. This means that any dbt macros in your project that use target settings will be executed by Qyra and resolved according to those settings.
Why use targets in macros?
You might want dbt to behave differently depending on the target/environment you're running in. Common use cases include:
- Using different schema naming conventions in dev vs. production
- Configuring different warehouse sizes (small for dev, large for prod)
- Adjusting query performance settings per environment
- Loading different amounts of data (sample data in dev, full data in prod)
- Applying different data retention policies
Let's look at a common example: customizing schema names based on your target.
Example: Schema naming based on target
By default, dbt appends your custom schema names to your target schema. For example, if your target schema is analytics and you configure a model with schema='staging', dbt creates it in analytics_staging.
This default behavior is useful in development because it prevents developers from overwriting each other's work - each developer can have their own target schema like dbt_jane or dbt_john, resulting in separated schemas like dbt_jane_staging and dbt_john_staging.
However, in production, you typically want cleaner schema names. Instead of analytics_staging, you just want staging. This is where customizing the generate_schema_name macro based on the target becomes helpful.
Default behavior:
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if custom_schema_name is none -%}
{{ default_schema }}
{%- else -%}
{{ default_schema }}_{{ custom_schema_name | trim }}
{%- endif -%}
{%- endmacro %}Modified behavior for production:
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if custom_schema_name is none -%}
{{ default_schema }}
{%- elif target.name == 'prod' -%}
{{ custom_schema_name | trim }}
{%- else -%}
{{ default_schema }}_{{ custom_schema_name | trim }}
{%- endif -%}
{%- endmacro %}This macro says: "If we're in production (target.name == 'prod'), use just the custom schema name. Otherwise, use the default behavior of prefixing it."
How this works in Qyra:
If your Qyra connection settings specify:
- Target name:
prod - Dataset/Schema:
analytics
And you have a model configured with:
{{ config(schema='staging') }}Then Qyra will create/query this model in the staging schema - not analytics_staging - because target.name == 'prod'.
This gives you clean, professional schema names in production (staging, marts, reporting) while maintaining developer isolation in development environments (dbt_jane_staging, dbt_john_staging).
The generate_schema_name macro is a default dbt macro and its behavior can be overridden by adding a macro to dbt with the same name. If this macro is not showing up in your dbt project, it means that your project is using the default macro. It is a good idea to check your project for custom macros which can alter your project's configurations.
Example: Warehouse configuration
Similarly, if you have a macro that dynamically configures settings:
{% macro get_warehouse() %}
{% if target.name == 'prod' %}
{{ return('LARGE_WAREHOUSE') }}
{% elif target.name == 'dev' %}
{{ return('SMALL_WAREHOUSE') }}
{% endif %}
{% endmacro %}And use it in your models:
{{ config(snowflake_warehouse=get_warehouse()) }}Qyra will use the configuration that corresponds to the target name you've configured in your Qyra connection settings.
Differences between dbt Core and dbt Cloud
In dbt Core, teams commonly use targets to manage different environments. In dbt Cloud, targets are replaced by environments, and custom logic such as if target.name = dev then... is commonly swapped out using env vars to become 'if env_var = development then...' or similar.
dbt environment variables
What are environment variables?
Environment variables allow you to set dynamic values that your dbt macros can reference. This is particularly useful if your dbt project uses environment variables to control behavior (common in dbt Cloud setups).
Configuring environment variables in Qyra
If your dbt project uses environment variables in your dbt profiles.yml file, you can add these to Qyra in your project settings. For each environment variable, you'll need to add the key + value pair for the item.
You'll normally find these values in a file called .env in your dbt project directory.
For example, you might have something like:
profile:
target: prod
outputs:
prod:
type: postgres
host: 127.0.0.1
user: "{{ env_var('DBT_USER') }}"
....Then a .env file like:
export DBT_USER="myspecialuser"So, in Qyra, you'd add a new environment variable and put key as DBT_USER and value as myspecialuser.
Reserved and blocked variable names
When you create or update a project, Qyra validates each environment variable key and rejects values that could change how dbt or its child processes execute. Names must contain only letters, numbers, and underscores, and cannot start with a number.
The following keys are blocked:
GIT_ASKPASS,GIT_SSH,GIT_SSH_COMMAND, and any name starting withGIT_CONFIG_LD_AUDIT,LD_LIBRARY_PATH,LD_PRELOAD, and any name starting withDYLD_NODE_OPTIONS,NODE_PATHPATH,SHELLPERL5OPT,PYTHONHOME,PYTHONPATH,RUBYOPTSSH_ASKPASS- Any name starting with
QYRA_DBT_PROFILE_VAR_(reserved for internal use)
If you have an existing project that already uses one of these keys, project compilation will continue to work — the check only runs when you save changes to your connection settings. Rename the variable in your dbt project and save the updated settings to clear the validation error.
How environment variables work with dbt macros
When Qyra compiles your dbt project, any environment variables you configure will be available to your dbt macros through the env_var() function.
For example, if you have a macro that checks an environment variable:
{% macro get_schema() %}
{% if env_var('DBT_ENV') == 'production' %}
{{ return('prod_analytics') }}
{% else %}
{{ return('dev_analytics') }}
{% endif %}
{% endmacro %}You would set DBT_ENV=production as an environment variable in your Qyra connection settings to ensure the macro resolves to prod_analytics.
Key points about environment variables:
- They're available to all macros in your dbt project via the
env_var()function - They're particularly important if your dbt project was built for dbt Cloud, which relies heavily on environment variables
- Make sure to set any environment variables your macros depend on in Qyra to match your production dbt environment
Key takeaways
- Qyra compiles your dbt project using the target name and other settings you configure in the connection settings
- Any macros that use
target.name, environment variables, or other profile settings will resolve according to your Qyra configuration - Make sure your Qyra target name matches the target you use for your production dbt runs to ensure Qyra behaves consistently with your dbt project
- If you have custom macros that depend on target settings or environment variables, ensure those settings are configured correctly in your Qyra connection settings
If you're experiencing unexpected behavior with how Qyra compiles your dbt project, check that your target name in Qyra matches what your dbt macros expect, and verify any environment variables your macros depend on are properly set.
dbt packages
Running dbt deps installs the packages listed in packages.yml and writes package-lock.yml, which records the exact version of every installed package plus a sha1_hash fingerprint of the whole dependency tree. On later runs, dbt installs the versions pinned in the lock file instead of re-resolving the ranges in packages.yml, so every environment gets the same versions.
packages.yml can specify version ranges:
packages:
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]
- package: calogica/dbt_expectations
version: ">=0.8.0"package-lock.yml pins what those ranges resolved to:
packages:
- package: dbt-labs/dbt_utils
version: 1.1.1
name: dbt_utils
- package: calogica/dbt_expectations
version: 0.10.1
name: dbt_expectations
sha1_hash: b980d166c84935d6d661192cf31db5144295c1afCommit package-lock.yml to version control. Qyra runs dbt deps when it compiles your project server-side (the "Refresh dbt" button, qyra refresh, and the refresh API), so a committed lock file makes Qyra install the same package versions as your local and CI runs — the same reason it keeps your team, CI, and production consistent.
To update packages, edit the version ranges in packages.yml (or delete package-lock.yml to force a fresh resolution), run dbt deps, test the change, and commit both files together.
Private packages
Qyra runs dbt deps on its own servers, so a package hosted in a private repository only installs if the credentials to read that repository are available where dbt runs.
Over the GitHub connector, this works with no extra configuration. Qyra passes the same credentials it used to clone your project — the GitHub App installation token or the personal access token on the connection — to dbt deps. Package URLs written in SSH form are rewritten to HTTPS and authenticated with those credentials, so both of these install:
packages:
- git: "git@github.com:my-org/my-private-package.git"
revision: v1.0.0
- git: "https://github.com/my-org/my-other-package.git"
revision: v2.1.0The credentials must be able to read the package repository as well as the project repository:
- GitHub App: install the app on the package repository too. If it is missing,
dbt depsfails and the error tells you to add the dependency to the same installation. - Personal access token: the token needs read access to the package repository.
This also applies to GitHub Enterprise, using the host domain set on the connection.
SSH keys are never used. The image that runs dbt has no SSH client, so git cannot fall back to SSH. Over the GitHub connector the SSH-form URL above is rewritten to HTTPS before dbt runs, so it still works. On every other connection type an SSH package URL fails with error: cannot run ssh: No such file or directory.
On other connection types — GitLab, Azure DevOps, Bitbucket, and dbt Cloud — the connection's credentials are not available to dbt deps. Declare the package over HTTPS with a token supplied as a dbt environment variable:
packages:
- git: "https://{{ env_var('DBT_ENV_SECRET_PKG_TOKEN') }}@github.com/my-org/my-private-package.git"
revision: v1.0.0Set DBT_ENV_SECRET_PKG_TOKEN under Advanced configuration options → Environment variables on the dbt connection, and use a read-only token scoped to the package repository. The DBT_ENV_SECRET_ prefix tells dbt to mask the value in its logs.
In a project with multiple dbt sources, every source resolves its own packages with its own connection's credentials, and environment variables are scoped to a single source. A token needed by two sources must be set on both.
For more on dbt package management, see the dbt packages documentation.