GitHub Sync (GitOps)
Two-way, PR-based sync of your models and saved queries with a GitHub repo as a dbt project
Connect a GitHub repository to your organization and CH-UI keeps your models and saved queries in sync with it as a dbt project. Edits in the UI open pull requests; merging a PR pulls the change back into the workspace. Your main branch becomes the source of truth.
Available on Pro and Enterprise plans. On the Free plan the integration is hidden (the API returns 402).
How it works
Once a repo is connected, the source of truth flips:
mainbranch = the truth. The.sql/.ymlfiles in your repo are the canonical definition of each model and saved query.- Your CH-UI workspace = a read cache of what's on
main. - Edits flow through PRs, not direct saves. You edit a model in the UI → CH-UI opens a pull request → you review and merge on GitHub → the merge fires a push webhook → CH-UI pulls
mainand updates its cache.
A synced object is marked git-managed and becomes read-only-by-direct-edit in the UI — the Save action opens a PR instead.
UI edit ──▶ Pull Request ──▶ (review) ──▶ merge ──▶ push webhook ──▶ CH-UI pulls mainSetup
GitHub configuration lives in the Console at Org → GitHub (owners and admins only).
- Install the GitHub App. Click Install on GitHub, choose the account or org, and grant access to a single repository. CH-UI uses a GitHub App (per-installation tokens) — no personal access tokens or OAuth.
- Pick the repo. Choose the repository, the default branch (defaults to
main), and a base path (defaults tochui) — the folder inside the repo where the dbt project lives. - Set a default connection. Models pulled from Git that don't name a connection bind to this connection (see Connections).
The repo must have at least one commit on the base branch (e.g. an initial README). A completely empty repo can't receive a pull request.
File layout
CH-UI reads and writes a real dbt project, rooted at the base path:
chui/
dbt_project.yml # minimal dbt project (profile 'chui')
models/
<model>.sql # {{ config(...) }} header + SELECT
<model>.yml # version: 2 properties (description, columns, data_tests)
sources.yml # version: 2 source definitions (if any)
analyses/
<saved-query>.sql # saved queries (dbt's home for stored, non-run SQL)
tests/
<name>.sql # singular tests (from custom-SQL data tests)A model's name is its filename (dbt convention). The .yml only adds properties.
Example models/daily_users.sql:
{{ config(materialized='incremental', engine='MergeTree()', order_by='(event_date)', incremental_column='event_date', connection='production') }}
select event_date, count() as users
from {{ source('raw', 'events') }}
group by event_dateExport (seed the repo)
For a fresh repo, click Export to GitHub on the GitHub page. CH-UI serializes every model and saved query in your workspace into the dbt layout and opens a single seeding PR. Nothing changes in CH-UI until you merge it — at which point the objects are pulled back and marked git-managed.
Pull (Git → CH-UI)
When a PR merges into the configured branch, GitHub sends a push webhook and CH-UI:
- Upserts models, saved queries, and sources by name, marking them git-managed.
- Syncs each model's data tests from its
.yml. - Deletes git-managed objects whose files were removed from the repo.
- Records the synced commit SHA and timestamp.
You can also trigger this manually with Sync now on the GitHub page (useful for the first import or recovery).
Editing synced objects
Saving a git-managed model or saved query directly returns 409. The editor catches this and opens a pull request with your change instead, then links you to it. Merge the PR to apply the change — it flows back through the pull path.
To delete a git-managed object, delete its file in the repo (via a PR).
dbt compatibility
CH-UI's model engine is file-compatible with dbt-core for ClickHouse, so you can bring existing dbt models in. See Models for the full templating reference. In short:
{{ ref('model') }},{{ source('name', 'table') }}, and{{ this }}are resolved at run time (legacy$ref(model)still works).{{ config(...) }}headers map to the dbt-clickhouse adapter:materialized,engine,order_by,unique_key,database, plus CH-UI'sincremental_columnandconnection.- Generic column tests in
schema.yml(not_null,unique,accepted_values,relationships) map to CH-UI data tests.
Connections by name
dbt has no per-model connection (it uses one profile per run), so CH-UI carries it as a custom config key: {{ config(connection='production') }}. Models without it bind to the default connection you set when connecting the repo. This lets a single repo span multiple connections, and lets plain dbt files (with no connection key) import cleanly.
Limitations
- No full Jinja. Macros,
{{ var() }},{% if %}/ loops,is_incremental(), seeds, and snapshots are not evaluated. Files using them still import, but are flagged "contains unsupported Jinja" and may not run as-is. CH-UI'sincremental_columnwatermark approximatesis_incremental(). - Singular (custom-SQL) tests are written out to
tests/*.sqlon export but are not re-imported on pull (dbt singular tests carry no metadata tying them to a model). The four generic column tests round-trip fully.
Disconnecting
Disconnect on the GitHub page drops the integration in CH-UI. Your synced objects stay in the workspace. To fully unbind, also Uninstall the App from GitHub.