This is the full developer documentation for SQLAnvil # SQLAnvil > Define your data transformations in SQL. SQLAnvil compiles, versions, and runs them against BigQuery, PostgreSQL, Supabase, or MySQL/MariaDB. ## Use cases [Section titled “Use cases”](#use-cases) Integrate a second Postgres database Operational data lives in Supabase, but a separate PostgreSQL database holds data that needs to be merged in. SQLAnvil orchestrates cross-database joins and writes the unified result back into Supabase schemas. Enrich Supabase with BigQuery public data Join Supabase operational tables against public datasets in BigQuery — ZIP code demographics, postal boundaries, currency rates — and sync the enriched rows back into Supabase for use in your app. [How & why →](/docs/use-cases/enrich-supabase-with-bigquery/) ML training data pipeline Curate, clean, and version training datasets directly in Supabase using SQLX views and incremental tables. Downstream ML pipelines read from stable, tested data models instead of raw tables. BigQuery reporting warehouse Pull data from multiple sources — Supabase, Postgres, external APIs — into a curated BigQuery warehouse. SQLAnvil models, tests, and refreshes the reporting layer on a schedule. eCommerce product feed for Google Ads Transform your eCommerce catalog in Supabase or Postgres into a clean, structured dataset and export it as JSON for Google Ads product feeds — with assertions to catch missing prices or broken image URLs before they go live. *** ## What Dataform doesn’t do [Section titled “What Dataform doesn’t do”](#what-dataform-doesnt-do) **No Supabase or Postgres support.** Dataform OSS dropped non-BigQuery adapters. If your data isn’t in BigQuery, it can’t help you. **Google-centric by design.** Dataform’s open-source version exists to serve Google Cloud workflows. Features, roadmap, and documentation all assume BigQuery — everything else is out of scope. **SQLAnvil cares about your data, wherever it lives.** *** ## What SQLAnvil does [Section titled “What SQLAnvil does”](#what-sqlanvil-does) BigQuery Full support for BigQuery — partitioning, clustering, labels, materialized views, incremental upserts via `MERGE`. PostgreSQL Idiomatic Postgres DDL — native partitioning, `INSERT … ON CONFLICT` upserts, btree/gin/gist indexes, tablespaces. Supabase Extends Postgres with RLS policies, Realtime publications, pgvector indexes, and Supabase Wrappers (FDW). MySQL / MariaDB One adapter for MySQL 8 and MariaDB 11 — portable DDL, `ON DUPLICATE KEY UPDATE` upserts, a `mysql:{}` block for indexes/engine/charset, COMMENT metadata, and materialized-view emulation. SQLX + YAML + JS Write SQL with config blocks, use `actions.yaml` for bulk definitions, or drive everything via the JavaScript API. *** SQLAnvil is an open-source fork of [Dataform OSS](https://github.com/dataform-co/dataform) (Apache 2.0), extended with first-class PostgreSQL and Supabase support. It is not affiliated with or endorsed by Google. # SQLAnvil Cloud > The hosted control plane for SQLAnvil — connect a repo, configure warehouses, run branch CI, and schedule workflows from a web dashboard. SQLAnvil Cloud is the hosted **control plane** for your SQLAnvil projects. The open-source CLI compiles and runs your SQL workflows; Cloud adds the layer around it — connecting your repo, brokering warehouse connections, running branch CI on pull requests, scheduling workflows, and giving you a web dashboard to review and operate it all. It runs at **[app.sqlanvil.com](https://app.sqlanvil.com)** and is **free during early access** — see [pricing](https://sqlanvil.com/pricing/) for the planned plans (billing will only ever start with advance notice and your explicit agreement). ## What Cloud adds over the CLI [Section titled “What Cloud adds over the CLI”](#what-cloud-adds-over-the-cli) | | Open-source CLI | SQLAnvil Cloud | | ----------------------- | --------------- | -------------- | | Compile + run SQL | ✅ | ✅ (hosted) | | Selection (tags, deps) | ✅ | ✅ | | Branch CI on PRs | DIY | ✅ turnkey | | Scheduled workflows | DIY (cron) | ✅ | | Run history + dashboard | — | ✅ | | Connect a GitHub repo | — | ✅ | Cloud is **agent-native and self-service** — there’s no IDE to learn and no support desk. You author with your own tools (CLI, agents), and Cloud handles review and operation. ## The shape of a project [Section titled “The shape of a project”](#the-shape-of-a-project) 1. **[Sign in with GitHub](#sign-in)** and create a project. 2. **[Connect a repo](/docs/cloud/connect-a-repo/)** — install the SQLAnvil GitHub App and pick the repository that holds your SQL project. 3. **[Configure a warehouse connection](/docs/cloud/connections/)** — the target your models run against. 4. Open pull requests → SQLAnvil runs **branch CI** and reports back on the PR. 5. **[Set up workflows](/docs/cloud/workflows/)** to run on a cadence. ## Sign in [Section titled “Sign in”](#sign-in) Cloud uses **Sign in with GitHub** — the same account that hosts your code. There’s no separate password to manage; GitHub verifies your identity. The first time you connect a repository you’ll also install the SQLAnvil GitHub App (a separate step — see [Connect a repo](/docs/cloud/connect-a-repo/)). ## Next steps [Section titled “Next steps”](#next-steps) * [Getting started with Cloud](/docs/cloud/getting-started/) — the full zero-to-green-PR path * [Connect a repo](/docs/cloud/connect-a-repo/) * [Warehouse connections](/docs/cloud/connections/) — including [Connect Supabase](/docs/cloud/connections/supabase/) * [Workflows](/docs/cloud/workflows/) * [The Code workspace](/docs/cloud/workspace/) — browse branches, preview results, see the DAG * [The MCP server](/docs/cloud/mcp/) — operate and debug from Claude Code or any MCP client * [Compilation overrides](/docs/cloud/compilation-overrides/) * [The ingest token](/docs/cloud/ci/ingest-token/) * [Data handling](/docs/cloud/data-handling/) — what Cloud stores, and what it never sees # Billing & your account > What SQLAnvil Cloud costs today, how billing will work, and how to cancel, delete your account, and understand what happens to your data when you leave. ## What it costs today [Section titled “What it costs today”](#what-it-costs-today) **SQLAnvil Cloud is free during early access.** The [pricing page](https://sqlanvil.com/pricing/) shows the planned plans; nothing is metered or charged right now. When billing starts, it will start **with advance notice**, and you will never be charged without explicitly agreeing to a paid plan first — that’s a commitment in the [Terms of Service](https://sqlanvil.com/terms/), not just a promise on this page. There is no card on file to forget about, because we don’t collect one. ## Canceling [Section titled “Canceling”](#canceling) There’s no subscription to cancel during early access — stopping is entirely self-service: 1. **Stop running** — pause or delete [Workflows](/docs/cloud/workflows/), or close PRs; nothing runs unless a PR or schedule triggers it. 2. **Disconnect integrations** — uninstall the SQLAnvil GitHub App from your repository, and revoke the Supabase authorization (Settings → Supabase warehouse, or from your Supabase account’s authorized apps). Revoking ends our ability to run against your warehouse immediately. 3. **Delete projects** — deleting a project removes its runs and its stored configuration. ## Deleting your account [Section titled “Deleting your account”](#deleting-your-account) To delete your account and all associated data, email **from the email address on your account**. We confirm and complete deletion within 30 days, per the [Privacy Policy](https://sqlanvil.com/privacy/). ## What happens to your data when you leave [Section titled “What happens to your data when you leave”](#what-happens-to-your-data-when-you-leave) * **Your warehouse is untouched** — tables SQLAnvil built for you live in *your* database and stay there. Nothing about leaving Cloud affects your data or your warehouse. * **Run detail** (compiled SQL + error text) is scrubbed 30 days after each run finishes regardless — see [Data handling](/docs/cloud/data-handling/). * **Deleting a project** removes its runs and configuration immediately. * **Authorization tokens** are deleted when you disconnect, revoke, or delete the project. We never held your database password to begin with. * **Your code** was always in your GitHub repo — there’s nothing to export from us. # The ingest token > A per-project secret that lets your CI report SQLAnvil run results back to the Cloud dashboard. The **ingest token** lets a CI job report run results back to your SQLAnvil Cloud dashboard. It powers the **self-hosted branch-CI** path: execution runs in your own GitHub Actions (your compute), and the result — status + log — is POSTed to Cloud so it shows up alongside your other runs. This is distinct from the GitHub App’s webhook path (which creates runs directly). The ingest token is specifically for *your* CI calling *us*. ## Generate it [Section titled “Generate it”](#generate-it) The token is issued on demand, and only once a repo is connected (a token has no purpose without a repo for CI to live in). 1. In the project’s setup checklist, under **Generate an ingest token**, click **Generate token**. 2. Copy it immediately — only a hash is stored, so it’s shown **once**. (You can regenerate anytime, which invalidates the old one.) 3. Add it to your repository as a secret named **`SQLANVIL_CLOUD_TOKEN`**, and set a repository variable **`SQLANVIL_CLOUD_URL`** to `https://app.sqlanvil.com`. ## How CI uses it [Section titled “How CI uses it”](#how-ci-uses-it) Your branch-CI workflow POSTs the run result to the ingest endpoint: ```bash curl -X POST "$SQLANVIL_CLOUD_URL/api/ingest/runs" \ -H "Authorization: Bearer $SQLANVIL_CLOUD_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "trigger": "branch_ci", "status": "success", "gitBranch": "my-feature", "prNumber": 42, "log": "..." }' ``` The token authenticates the request to *your* project; the run then appears in the dashboard. Keep it secret — treat it like any other CI credential. # Compilation overrides > Per-project overrides that shape how your SQL compiles — default database, schema suffix, and table prefix. **Compilation overrides** change how your project’s SQL compiles without editing `workflow_settings.yaml` — useful for separating environments (dev vs. prod) on the same codebase. Set them from **Settings → Compilation overrides**. ## Fields [Section titled “Fields”](#fields) * **Default database** — the database the compiled SQL targets. For BigQuery this is the project id; for Postgres/Supabase it’s the database name. * **Schema suffix** — appended to every schema name, so environments don’t collide. For example a suffix of `dev` turns schema `analytics` into `analytics_dev`. (This mirrors Dataform’s workspace suffix.) * **Table prefix** — prepended to every table/view name. Leave blank for none. ## Why overrides instead of branches in the file [Section titled “Why overrides instead of branches in the file”](#why-overrides-instead-of-branches-in-the-file) Your `workflow_settings.yaml` declares the project’s defaults. Overrides let the *same* compiled project target a different schema or database per environment — the basis for running `dev` and `prod` from one repo without divergent config files. They’re applied at compile time, so the generated SQL reflects them before anything runs. ## Relationship to environments [Section titled “Relationship to environments”](#relationship-to-environments) Overrides pair with a project’s [warehouse connection](/docs/cloud/connections/) and, where used, named environments — together they determine *where* and *under what names* your models land. # Connect a repo > Install the SQLAnvil GitHub App and connect a repository so Cloud can read your project and run branch CI on pull requests. A SQLAnvil Cloud project is backed by a **GitHub repository** — the one holding your `.sqlx` files and `workflow_settings.yaml`. Connecting it is how Cloud reads your project to compile and run it, and how it receives pull-request events for branch CI. Connecting goes through the **SQLAnvil GitHub App**, so the connection is *verified* — Cloud only ever sees repositories you’ve explicitly granted it, and it uses short-lived installation tokens rather than a stored personal access token. ## Sign-in vs. the GitHub App [Section titled “Sign-in vs. the GitHub App”](#sign-in-vs-the-github-app) These are two separate things, and you’ll touch both: * **Sign in with GitHub** authenticates *you* (identity only). * **The GitHub App** grants Cloud scoped access to *specific repositories* (read contents, write PR checks) and delivers pull-request webhooks. You install it once per account/org and choose which repos it can see. ## Connect a repository [Section titled “Connect a repository”](#connect-a-repository) 1. In a project, open the setup checklist and choose **Connect a repo**. 2. If you haven’t installed the app yet, click **Install SQLAnvil on GitHub**. GitHub asks which account/org and which repositories to grant — pick “only select repositories” and choose the one holding your SQLAnvil project (you can add more later). 3. You’re returned to the project. **Connect a repo** now shows a dropdown of the repositories the app can access. Pick one and click **Connect**. The project now shows a verified repo connection. You can change it anytime from **Settings → Repo**. ## What Cloud can and can’t do [Section titled “What Cloud can and can’t do”](#what-cloud-can-and-cant-do) The app requests the minimum it needs: * **Contents: read** — to clone and compile your project. * **Pull requests / Checks: write** — to post the “SQLAnvil CI” status on a PR. * **Metadata: read** — required by all GitHub Apps. It has **no write access to your code** and can only see the repositories you selected at install time. Revoke or adjust access anytime from your GitHub account’s **Settings → Applications → Installed GitHub Apps**. ## Branch CI [Section titled “Branch CI”](#branch-ci) Once the repo **and** your [Supabase warehouse](/docs/cloud/connections/supabase/) are connected, opening a pull request creates a SQLAnvil **run**: SQLAnvil branches your Supabase project, runs the workflow against that ephemeral branch, and posts a “SQLAnvil CI” check back to the PR (visible in the dashboard with full run detail). For **Postgres/BigQuery/MySQL**, run execution in your own CI and report with [the ingest token](/docs/cloud/ci/ingest-token/) instead. # Warehouse connections > Tell SQLAnvil Cloud which warehouse your project targets — adapter type and region. Credentials are brokered, never stored. A project’s **warehouse connection** tells Cloud where your models run. You set it from **Settings → Warehouse**. ## Fields [Section titled “Fields”](#fields) * **Type** — the adapter SQLAnvil compiles for: `supabase`, `postgres`, `bigquery`, or `mysql`. This matches the `warehouse:` in your project’s `workflow_settings.yaml`. * **Region** — where your warehouse runs (e.g. `us-east-1`). Used to reach the right connection endpoint. These describe the *shape* of the connection. They’re safe to store and appear in the dashboard. ## Credentials are brokered, not stored [Section titled “Credentials are brokered, not stored”](#credentials-are-brokered-not-stored) SQLAnvil Cloud does **not** keep your database password or service-account key. Connections are **brokered at run time** — the runner obtains a short-lived credential for the specific run and discards it. This is the “custodian → broker” principle: Cloud orchestrates access without holding long-lived secrets. For branch CI, this goes a step further: rather than touching your production database, SQLAnvil provisions a disposable **ephemeral branch** of your warehouse, runs against that, and destroys it — so a PR never writes to production. See [Supabase connections](/docs/cloud/connections/supabase/). ## Compilation overrides [Section titled “Compilation overrides”](#compilation-overrides) Alongside the connection, a project carries **[compilation overrides](/docs/cloud/compilation-overrides/)** — default database, schema suffix, and table prefix — that shape how your SQL compiles per environment. # Supabase connections > How SQLAnvil Cloud connects to Supabase — the session pooler, and ephemeral branches for branch CI. Supabase is a first-class SQLAnvil warehouse (see the OSS [Supabase guide](/docs/guides/supabase/)). Cloud connects to it through a **revocable OAuth authorization** — you never give it your database password or service-role key. ## Connect your Supabase account [Section titled “Connect your Supabase account”](#connect-your-supabase-account) 1. On your project, go to **Settings → Supabase warehouse** and click **Connect Supabase**. 2. **Authorize** on Supabase’s consent screen. You don’t register anything or paste a key — SQLAnvil runs the OAuth app; you’re just granting it access. Choose the organization/project to grant. 3. **Pick the warehouse project.** If your account has more than one project, a dropdown appears — choose the one CI should branch off. (If you granted a single project, it’s selected automatically.) The project must be on the **Pro** plan — branch CI needs Supabase branching. That’s it. From then on, every PR runs against a fresh branch of that project. **What we hold.** Only a Supabase OAuth **refresh token**, encrypted at rest, from which each run mints a short-lived (1-hour) access token. Never your DB password or service-role key. You can **revoke** it any time in your Supabase account, or **Reconnect** to re-scope or re-authorize. See [Data handling](/docs/cloud/data-handling/). ## Use the session pooler, not the direct host [Section titled “Use the session pooler, not the direct host”](#use-the-session-pooler-not-the-direct-host) Supabase’s **direct** database host (`db..supabase.co`) is IPv6-only and won’t resolve from many runtimes. Always connect via the **session pooler**: * **Host:** `aws--.pooler.supabase.com` (the cluster number `` is shown in your project’s *Connect → Session pooler* dialog — it varies per project) * **Port:** `5432` (session mode — not the `6543` transaction pooler) * **User:** `postgres.` * **SSL:** `require` This is the connection shape Cloud builds automatically; it’s also what you’d put in a local `.df-credentials.json`. ## Branch CI uses ephemeral branches [Section titled “Branch CI uses ephemeral branches”](#branch-ci-uses-ephemeral-branches) For a pull request, SQLAnvil doesn’t touch your production database. It: 1. Creates a disposable **Supabase branch** of your project (via the Management API), 2. Runs the project against that fresh branch — models materialize and **assertions execute**, 3. Reports the result, then **destroys the branch** so nothing is left billing. Branching is available on Supabase **Pro** projects and works through the Management API directly — no GitHub-integration setup or `supabase/` migrations directory required. SQLAnvil owns the full ephemeral-environment lifecycle. ## Credentials [Section titled “Credentials”](#credentials) Cloud brokers the branch credentials at run time and never stores your database password — see [Warehouse connections](/docs/cloud/connections/). # Data handling > What SQLAnvil Cloud stores when it runs your project, where it lives, how long it's kept, and what you should never put in a model. SQLAnvil Cloud is a control plane: it orchestrates runs and shows you results. This page is the plain statement of what that means for your data — what we store, where, for how long, and the one thing you should keep out of your SQL. ## What we store [Section titled “What we store”](#what-we-store) When a run executes, we record, per action (table/view/assertion/operation/export/import): * **Metadata** — the target (`schema.name`), type, status, start/duration, the source file, and the identity the run connected as. Low-sensitivity; kept as run history. * **Run detail** — the **compiled SQL** for the action and, on failure, the **verbatim warehouse error**. This is what powers the run-detail view and “Copy failure context”. We also keep the run’s overall status, branch/PR/commit, and a tail of the run log. ## Where it lives, and who can read it [Section titled “Where it lives, and who can read it”](#where-it-lives-and-who-can-read-it) * Stored in our control-plane database (Supabase Postgres), **encrypted at rest**. * Protected by **row-level security**: a run and its detail are readable only by the project’s owner. Other customers cannot see your runs. * Run detail is stored in **plaintext** (not end-to-end encrypted). Operationally this means SQLAnvil *could* read it; we use it only to run and display your runs, never for anything else, and there is no support team browsing your data ([SQLAnvil Cloud is fully self-service](/docs/cloud/)). ## Retention [Section titled “Retention”](#retention) The **sensitive fields — the compiled SQL and the error text — are scrubbed 30 days after a run finishes.** The lightweight metadata row (target, status, timing) is kept so your run history stays intact. Deleting a project removes its runs. ## What we never store [Section titled “What we never store”](#what-we-never-store) * **Warehouse credentials.** They are brokered at run time, never custodied — see [Warehouse connections](/docs/cloud/connections/). * **Your table data.** We don’t copy rows out of your warehouse. We store the *SQL text* and *error text* of a run — not query results. (But note the next section: SQL and errors can *contain* data.) ## Important: don’t put secrets or sensitive data in your SQL [Section titled “Important: don’t put secrets or sensitive data in your SQL”](#important-dont-put-secrets-or-sensitive-data-in-your-sql) Because run detail is stored in plaintext, **treat your compiled SQL and warehouse errors as visible to the platform.** Two ways data can leak into them: * **Inlined literals** — `select 'my-secret-token' as t` or a hard-coded PII value lands verbatim in the stored compiled SQL. * **Error echoes** — a warehouse error can quote a row value (e.g. a unique-constraint violation printing an email address) into the stored error text. This is standard hygiene for *any* CI system (build logs are not a vault). The rule: * **Never inline secrets or sensitive values in models.** Reference secrets through your warehouse’s own mechanism, not SQL literals. * Keep PII out of hard-coded values, seed `VALUES (...)`, and assertion messages. If you have a hard requirement that the platform be unable to read run detail at all, that’s a client-side-encryption model — plaintext + RLS + 30-day scrub is the default today. If your team needs the stronger model, open a [GitHub Discussion](https://github.com/SQLAnvil/sqlanvil/discussions) or email . # Getting Started with Cloud > The full path from zero — author a project, connect your repo and Supabase, and get hosted branch CI on every PR. SQLAnvil Cloud runs your project’s CI **for you**: open a pull request, and it builds your workflow against a throwaway branch of your Supabase project, then reports pass/fail on the PR. You never hand it your database password — it runs on a short-lived, revocable authorization you can pull at any time. There are two parts: **author** the project (in your own tools), then **set it up in Cloud**. ## Part A — Author the project [Section titled “Part A — Author the project”](#part-a--author-the-project) Cloud runs a normal SQLAnvil project from a GitHub repo. If you don’t have one yet: 1. **Scaffold a project** — a `workflow_settings.yaml` + a `definitions/` folder. Start from the [`supabase-sqlanvil-starter`](https://github.com/SQLAnvil/supabase-sqlanvil-starter) template, or see the OSS [Getting Started](/docs/getting-started/). 2. **Pin the core version.** In `workflow_settings.yaml`, set `warehouse: supabase` and pin **`sqlanvilCoreVersion:`** to the current release — `sqlanvil init` writes this for you, and [What’s New](/docs/whats-new/) shows the latest version. Pinning a current version keeps the hosted runner fast (no per-run install). 3. **Author + push.** Build your models with the CLI, the Claude skill, or the VS Code extension, and push the repo to GitHub. ## Part B — Set it up in Cloud [Section titled “Part B — Set it up in Cloud”](#part-b--set-it-up-in-cloud) 1. **Sign in.** Go to [app.sqlanvil.com](https://app.sqlanvil.com) → **Continue with GitHub**. Your GitHub account *is* your identity — no separate password. 2. **Create a project.** Give it a name. The setup checklist guides the rest. 3. **Connect the repo.** **Install SQLAnvil on GitHub**, grant access to your repository, then pick it from the dropdown and **Connect**. See [Connect a repo](/docs/cloud/connect-a-repo/). 4. **Connect Supabase.** In **Settings → Supabase warehouse → Connect Supabase**, authorize once. If your account has more than one project, pick the **warehouse project** to branch off — it must be on the **Pro** plan (branch CI needs Supabase branching). You never register anything or paste a key: SQLAnvil runs the OAuth app; you just click Connect, and we store only a **revocable authorization**, never your DB credentials. See [Connect Supabase](/docs/cloud/connections/supabase/). 5. **Open a pull request.** SQLAnvil mints a scoped, short-lived token, creates an **ephemeral branch** of your Supabase project, runs your workflow against it, posts a **SQLAnvil CI** check on the PR, and destroys the branch. Green or red, right on the PR. 6. **Debug from the run page.** Every run shows each action’s status, timing, the executed SQL, and — on failure — the verbatim error plus **Copy failure context** (a one-click bundle to paste into an AI agent). 7. **Schedule a workflow** *(optional).* On the project’s **Schedules** page, add a cron schedule with an optional tag selection. See [Workflows](/docs/cloud/workflows/). ## Not on Supabase? [Section titled “Not on Supabase?”](#not-on-supabase) Hosted branch CI targets **Supabase** today: it relies on [Supabase branching](https://supabase.com/docs/guides/deployment/branching), which requires a Supabase project on their **Pro plan** (a free-tier project can’t run branch CI). For **Postgres, BigQuery, or MySQL** — or Supabase without branching — run execution in your own CI (e.g. GitHub Actions) and report results to the dashboard with an [ingest token](/docs/cloud/ci/ingest-token/) — same run history and PR checks, your compute. ## What Cloud does and doesn’t do [Section titled “What Cloud does and doesn’t do”](#what-cloud-does-and-doesnt-do) * **Authoring** stays in your tools — the CLI and your AI agents. Cloud doesn’t replace your editor. * **Reviewing and operating** — run history, run detail, schedules, PR checks — is the dashboard’s job. * **Credentials** — we broker, we don’t custody. See [Data handling](/docs/cloud/data-handling/). * **Two things hosted runs reject at compile time**, with a clear error before anything is provisioned: *local* file paths on [exports](/docs/guides/exports/)/[imports](/docs/guides/imports/) (the runner’s disk is ephemeral — stage on `s3://`/`gs://` instead), and [python script actions](/docs/guides/python-actions/) (user code doesn’t execute on the shared runner — run those projects with the local CLI or your own CI). # MCP server > Connect Claude Code or any MCP client to SQLAnvil Cloud — list projects and runs, debug failures with executed SQL, trigger workflows, and cut releases. SQLAnvil Cloud exposes an **MCP server** (Model Context Protocol, Streamable HTTP) so AI agents can operate and debug your hosted projects directly: inspect run history, pull a failure’s executed SQL and verbatim warehouse errors, trigger workflow runs, cut releases, and refresh catalogs. It is deliberately **not an authoring surface** — you edit and compile your project in your own repo with your own tools. The MCP covers what’s hosted: runs, workflows, releases, and their state. ## Connect [Section titled “Connect”](#connect) 1. Create a personal API token at **app.sqlanvil.com → API tokens** (shown once). 2. Add the server to Claude Code: ```bash claude mcp add --transport http sqlanvil https://app.sqlanvil.com/api/mcp \ --header "Authorization: Bearer sa_…" ``` Any MCP client that speaks Streamable HTTP works the same way. Revoking the token in the dashboard cuts access immediately. ## Tools [Section titled “Tools”](#tools) | Tool | What it does | | ------------------------------- | ---------------------------------------------------------------------------- | | `list_projects` / `get_project` | Your projects; config, warehouse connection, per-branch catalogs | | `list_workflows` | Each workflow’s selection, schedule, release mode, enabled state | | `list_runs` / `get_run` | Run history with filters; full per-action detail + log | | `get_run_failure` | **The debugging bundle**: failed actions with executed SQL + verbatim errors | | `list_releases` | Pinned compilation snapshots (branch, environment, commit, core version) | | `run_workflow_now` | Enqueue an immediate run; returns a run id to poll | | `cut_release` | Compile the branch head into a new pinned snapshot | | `refresh_catalog` | Recompile a branch’s tag/action/environment catalog | | `set_workflow_enabled` | Pause or resume a workflow’s schedule | Runs are asynchronous: operate tools return a `run_id` immediately; poll `get_run` for the outcome (runs typically take one to a few minutes). ## Data shapes [Section titled “Data shapes”](#data-shapes) Graph-adjacent payloads reuse the engine’s own JSON forms — action targets as `schema.name`, compiled SQL as `sqlanvil compile --json` emits it — so an agent that already knows the CLI’s output needs no second vocabulary. Everything an MCP tool returns is data the dashboard already shows you, scoped to your account by your token. # Workflows > Branch-bound, reusable run configurations — a graph selection plus an optional cron, run on demand or on a schedule against your real warehouse. A **workflow** is a named, reusable run configuration: *run **this selection** of the graph, against **this branch**, with **these dependency options** — on a schedule, or on demand.* It’s the hosted home for both your scheduled production runs and one-click “Run now” runs. Manage them from a project’s **Workflows** page. ## Fields [Section titled “Fields”](#fields) * **Name** — a label for the workflow. * **Branch** — the git branch the run compiles and executes (e.g. `main`). * **Tags / Actions** — the selection. Empty runs the full graph; otherwise only actions matching the chosen tags (`--tags`) and/or the explicitly chosen actions (`--actions`, by `schema.name`). Tags and actions compose as a union. The dropdowns are populated from the branch’s compiled **catalog** — use **Refresh** to recompile it after the branch changes (the editor shows which commit the catalog is from). * **Include dependencies** — also run the **upstream** actions the selection depends on (`--include-deps`). Needs a non-empty selection. * **Include dependents** — also run the **downstream** actions that depend on the selection (`--include-dependents`). Needs a non-empty selection. * **Environment** — an optional named environment (compilation overrides). * **Schedule** — optional. A standard 5-field cron expression, e.g. `0 6 * * *` (daily at 06:00), evaluated in the workflow’s timezone. Leave it off for an on-demand-only workflow. ## How it runs [Section titled “How it runs”](#how-it-runs) **Workflow runs execute against the real warehouse** — their writes are the point, and they persist. (Pull-request CI is different: it runs on a disposable database branch that’s destroyed afterwards. A workflow run is the production counterpart.) For Supabase warehouses connected via OAuth, no database password is ever stored: each run mints a short-lived, single-purpose database login through your Supabase connection, uses it for the run, and disables it immediately after. Objects it creates are owned by `postgres`, exactly as if you’d run the CLI yourself. Scheduled workflows fire from a periodic tick; **Run now** enqueues the same run immediately. If a workflow’s previous run is still going, the tick skips that firing rather than overlapping it. Every firing appears in the project’s run history tagged with its workflow, with full per-action detail. ## Releases — pinned compilation snapshots [Section titled “Releases — pinned compilation snapshots”](#releases--pinned-compilation-snapshots) By default a workflow compiles the **branch head** at run time. Setting **Run from → Latest release** pins it to a **release**: a frozen compilation of the branch (and environment) at an exact commit. Pinned workflows keep running that exact code even as the branch moves — until a new release is cut. * **Cut release** on the Workflows page compiles the branch head into a new snapshot (per environment, since environment overrides change the compiled SQL). * **Pushes to a pinned branch cut a release automatically** — merging to `main` refreshes what your scheduled workflows run, Dataform-release-config style. * Each release records the commit, action count, and the full compiled graph for audit; every run links back to the release it executed. ## Selection mirrors the CLI [Section titled “Selection mirrors the CLI”](#selection-mirrors-the-cli) A workflow is just a saved version of a run you could do by hand: ```bash sqlanvil run . --tags daily --include-dependents --environment prod ``` Tag/action selection and both dependency-expansion flags behave exactly like the CLI’s. # Code (read-only workspace) > Browse a branch's files with syntax highlighting, flip any model to its compiled SQL, and jump to VS Code — without a cloud editor. The **Code** page is a read-only workspace: pick a branch, browse the repo tree, and view any file with syntax highlighting. It is deliberately **not an editor** — authoring happens locally with your agent and git — but everything you’d want for *review* is here: * **Branch-scoped tree** — files come straight from GitHub via the SQLAnvil app installation; nothing is cloned or stored. * **Compiled SQL toggle** — for a `.sqlx` model, flip between the source and the SQL it compiled to. The compiled view comes from the branch’s **latest release** (pinned compilation snapshot) and is labeled with the commit it was compiled at — cut a release on the Workflows page if the branch doesn’t have one yet. * **Model metadata** — target, action type, tags, dependencies, and dependents, from the same compiled graph. * **Results preview** — for a model with a compiled release, the **Results** tab queries the warehouse directly (first 100 rows), two ways: * **Current data** reads the materialized object — what the last completed run produced. Run the workflow (*Run now*), wait for it to finish, and preview again to see the fresh data. * **Compiled query** executes the release’s stored SELECT without materializing anything — what the model *would* produce, even before it has ever run. For an assertion, rows returned are failures. Previews are read-only by construction: the SQL comes from the stored release (never from the browser), runs through the same revocable Supabase authorization as workflow runs, and is wrapped so anything but a single SELECT is rejected. Nothing is stored — rows go straight to your screen. * **Graph view** — the branch’s latest release drawn as a dependency DAG, data flowing left to right: tables, views, incrementals, assertions, sources and operations color-coded, each node clickable through to its file. Because it renders from the stored release, the graph always matches a specific commit (labeled), not a guess about the working tree. * **Open in VS Code** — one click opens the file in [github.dev](https://github.dev), the full VS Code web IDE against your own repo and GitHub login, for when you want real editor ergonomics. Together with run history and per-action detail, this completes the review loop: *read the model, read what it compiled to, read how it ran* — all without granting anything write access. # Getting Started > Install SQLAnvil, create a project, and run your first SQL workflow. SQLAnvil compiles SQLX files and action configs into SQL, then executes them against your data warehouse. Projects are driven by a `workflow_settings.yaml` file that declares your warehouse connection and default schema. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * Node.js 20 LTS or later * One of: a BigQuery project with a service account, a PostgreSQL database, a Supabase project, or a MySQL/MariaDB database ## Install [Section titled “Install”](#install) ```bash npm install -g @sqlanvil/cli ``` Or run without installing: ```bash npx @sqlanvil/cli --help ``` ## Create a project [Section titled “Create a project”](#create-a-project) 1. **Initialize** a new SQLAnvil project (pick your warehouse): ```bash sqlanvil init my-project --warehouse postgres # or: supabase | bigquery | mysql cd my-project ``` Prefer to be walked through it? **`sqlanvil init --interactive`** (1.23+) asks the same questions one at a time — including whether to start fresh or [convert an existing Dataform project](/docs/guides/migrate-dataform/) — and writes your `.df-credentials.json` from a credentials Q\&A, so steps 1–2 collapse into one. Want the opposite? `sqlanvil init --bare` scaffolds only the directories and config, no sample files. Every generated project also includes an **`AGENTS.md`** (1.24+) — a warehouse-tailored guide in the cross-agent standard that Codex, Cursor, Gemini CLI, and 30+ tools read natively, plus a `CLAUDE.md` bridge for Claude Code — so any [coding agent](/docs/guides/ai-agents/) pointed at your repo knows the sqlanvil dialect from the first prompt. The default creates a sample project in the shape real ones take — source declarations, staging views over them, output tables built from the staging layer, and a business-rule assertion: ```plaintext my-project/ ├── definitions/ │ ├── sources/ │ │ ├── app_orders.sqlx ← declaration: a table your app already has │ │ └── bigquery_zip_codes.sqlx ← declaration: BigQuery public data via a connection │ ├── intermediate/ │ │ ├── stg_app_orders.sqlx ← staging views over the sources │ │ └── stg_zip_codes.sqlx │ ├── outputs/ │ │ ├── sales/daily_sales.sqlx ← tables built from the staging views │ │ └── reporting/product_revenue.sqlx │ │ reporting/orders_by_region.sqlx ← joins both staged sources │ └── test/assert_sales_amounts_positive.sqlx ├── includes/ ← shared JavaScript helpers └── workflow_settings.yaml ← includes the bigquery_public connection ``` `compile` works immediately. To `run`, point the `app_orders` declaration at a real table (and add BigQuery credentials for the `zip_codes` source, or delete that pair) — `sqlanvil validate` tells you exactly what’s still missing. MySQL projects skip the BigQuery source (cross-warehouse connections need a Postgres/Supabase warehouse). 2. **Add your connection.** `init` already wrote `workflow_settings.yaml` (with your `warehouse:` and default schema) plus a `.df-credentials.json` template. Fill in the credentials file — it’s gitignored, so secrets never get committed: * PostgreSQL .df-credentials.json ```json { "host": "db.example.com", "port": 5432, "database": "analytics", "user": "sqlanvil_writer", "password": "...", "sslMode": "require", "defaultSchema": "public" } ``` * Supabase ```json // .df-credentials.json — Session pooler (copy the host verbatim from the dashboard Connect dialog) { "host": "aws-1-.pooler.supabase.com", "port": 5432, "database": "postgres", "user": "postgres.", "password": "...", "sslMode": "require", "defaultSchema": "public" } ``` * BigQuery ```json // .df-credentials.json — projectId + location. Auth via `gcloud auth application-default login`, // or add a "credentials" field containing a service-account key JSON. { "projectId": "my-gcp-project", "location": "US" } ``` * MySQL / MariaDB ```json // .df-credentials.json — no defaultSchema field: the defaultDataset in // workflow_settings.yaml IS the MySQL database. sslMode: "disable" for local Docker. { "host": "db.example.com", "port": 3306, "database": "analytics", "user": "sqlanvil_writer", "password": "...", "sslMode": "require" } ``` 3. **Write your first action** (or start from the generated demo models) in `definitions/my_view.sqlx`: ```sql config { type: "view", description: "My first SQLAnvil view." } SELECT 1 AS id, 'hello' AS greeting ``` 4. **Compile** to inspect the SQL that will run (no database needed): ```bash sqlanvil compile . ``` 5. **Run** against your warehouse: ```bash sqlanvil run . ``` With the sample project this stages your orders, builds `daily_sales` and the reporting tables, and checks the assertion. `run` reads `.df-credentials.json` from the project directory by default; pass `--credentials ` only to use a differently-named or relocated file. ## Project structure [Section titled “Project structure”](#project-structure) | Path | Purpose | | ------------------------ | -------------------------------------------------- | | `workflow_settings.yaml` | Warehouse connection, default schema/dataset, vars | | `definitions/` | SQLX files, SQL files, `actions.yaml`, JS files | | `includes/` | Shared JS macros and constants | | `package.json` | Declares `@sqlanvil/core` version and any packages | ## Action types [Section titled “Action types”](#action-types) | Type | Creates | | ------------- | -------------------------------- | | `table` | A full-replace table | | `view` | A SQL view | | `incremental` | An incrementally-updated table | | `assertion` | A data quality test | | `operation` | Arbitrary SQL statements | | `declaration` | A reference to an external table | See the **Reference** section in the sidebar for full API documentation on each action type. # Use with AI agents > Give coding agents (Claude Code, Codex, Cursor, Gemini CLI, and others) working knowledge of sqlanvil — the official Agent Skill, and the Cloud MCP server for operating runs. sqlanvil is a Dataform fork, and coding agents know Dataform — which means their instincts are *mostly* right and occasionally, specifically wrong (BigQuery config blocks, credential shapes, `;` separators, a `dataform` CLI that isn’t there). Two official integrations close that gap. ## The Agent Skill (authoring) [Section titled “The Agent Skill (authoring)”](#the-agent-skill-authoring) The **`sqlanvil-engineering-fundamentals`** skill teaches an agent the exact deltas between Dataform/BigQuery habits and correct sqlanvil code — config blocks, credentials, DDL, statement separators, cross-warehouse connections, the MySQL inversions, and the CLI. It follows the open [Agent Skills](https://agentskills.io) format supported by Claude Code, Codex CLI, Cursor, Gemini CLI, GitHub Copilot, and dozens of other tools. Install it with the [skills CLI](https://skills.sh), which detects your installed agents: ```bash npx skills add SQLAnvil/agent-skills ``` Or install manually: copy `skills/sqlanvil-engineering-fundamentals/` from [SQLAnvil/agent-skills](https://github.com/SQLAnvil/agent-skills) into your agent’s skills directory (for Claude Code, `~/.claude/skills/`). The skill is versioned with the engine — it pins the current release and marks version-gated features (e.g. `≥1.22`), and is updated as part of every sqlanvil release. ## AGENTS.md in every project (1.24+) [Section titled “AGENTS.md in every project (1.24+)”](#agentsmd-in-every-project-124) Projects generated by `sqlanvil init` (all modes), `sqlanvil migrate-dataform`, and the Cloud wizard include a warehouse-tailored **`AGENTS.md`** — the repo-scoped guide in the cross-agent [AGENTS.md standard](https://agents.md) — plus a one-line `CLAUDE.md` bridge for Claude Code. That covers agents working *inside one of your project repos* with zero installation; the skill below adds the fuller guide to the agent itself, across all repos. Pre-existing `AGENTS.md`/`CLAUDE.md` files are never overwritten. ## llms.txt (docs for agents) [Section titled “llms.txt (docs for agents)”](#llmstxt-docs-for-agents) The full documentation is also published in the [llms.txt](https://llmstxt.org) format for web-capable agents: [`/llms.txt`](/llms.txt) (index), [`/llms-small.txt`](/llms-small.txt) (abridged), and [`/llms-full.txt`](/llms-full.txt) (complete docs in one file). ## The MCP server (operating, Cloud) [Section titled “The MCP server (operating, Cloud)”](#the-mcp-server-operating-cloud) The skill covers *writing* projects; for *operating* them on [SQLAnvil Cloud](https://app.sqlanvil.com) — inspecting runs, debugging failures, checking workflow status — connect your agent to the remote MCP server at `https://app.sqlanvil.com/api/mcp`. See the [MCP server guide](/docs/cloud/mcp/) for token setup and the available tools. ## Which do I need? [Section titled “Which do I need?”](#which-do-i-need) | You want an agent to… | Use | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | Write or edit `.sqlx` models, configs, credentials | the Agent Skill | | Convert a Dataform project | the Agent Skill + [`sqlanvil migrate-dataform`](/docs/guides/migrate-dataform/) | | Investigate a failed Cloud run, check schedules | the MCP server | | Answer business questions about your data (BI copilots, text-to-SQL) | [warehouse-persisted metadata](/docs/guides/metadata/) | | All of the above | both — they’re complementary | # Artifacts & Catalog > SQLAnvil writes your compiled graph and run history to Parquet under target/ — query it with SQL (sqlanvil query), summarize it (sqlanvil inspect), or generate an HTML catalog (sqlanvil docs). Every `compile` and `run` writes **queryable artifacts** under `target/` — a catalog of your models and a history of your runs, as **Parquet**. Query them with SQL, summarize them, or generate a browsable HTML catalog. No warehouse connection needed to read them. Note Requires `@sqlanvil/cli` **1.10.0** or newer. Add `target/` to your `.gitignore`. ## What gets written [Section titled “What gets written”](#what-gets-written) * `compile` → `target/catalog/{actions,dependencies,columns}.parquet` (models, object-level dependency edges, documented columns). * `run` → `target/runs/run_.parquet` (one row per action: status, timing, error). Writing is **best-effort** — it never fails your compile/run. Disable with `--no-artifacts`. ## Query it [Section titled “Query it”](#query-it) ```bash sqlanvil query "select type, count(*) as n from actions group by 1 order by 1" sqlanvil query "select readable_name, error_message from runs where run_id = (select max(run_id) from runs) and status = 'FAILED'" sqlanvil query "select * from dependencies" --json ``` Views available: **`actions`**, **`dependencies`**, **`columns`**, **`runs`**. It’s just DuckDB SQL — join, filter, aggregate however you like. The Parquet files are also readable directly by pandas/polars, DuckDB, or as external tables in BigQuery/Snowflake. ## Summarize it [Section titled “Summarize it”](#summarize-it) ```bash sqlanvil inspect ``` Prints action counts by type, the latest run’s status/timing, and any recent failures. `--json` for a structured summary. ## Browse it [Section titled “Browse it”](#browse-it) ```bash sqlanvil docs # writes target/docs/index.html ``` A self-contained HTML catalog — models with their type, tags, description, columns, what they depend on, and last-run status, with a filter box. One file, no server; open it in a browser. # BigQuery > Configure SQLAnvil for Google BigQuery — connection, partitioning, clustering, labels, and incremental tables. ## Connection [Section titled “Connection”](#connection) Non-secret settings in `workflow_settings.yaml` (committed): workflow\_settings.yaml ```yaml warehouse: bigquery defaultProject: my-gcp-project # GCP project ID defaultLocation: US # BigQuery region defaultDataset: analytics # default dataset for actions sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this ``` Auth lives in `.df-credentials.json` (gitignored). For BigQuery you typically only need the project and location — credentials resolve via [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) (`gcloud auth application-default login` locally, or an attached service account in CI): .df-credentials.json ```json { "projectId": "my-gcp-project", "location": "US" } ``` To use an explicit service account instead of ADC, add its key JSON as a `credentials` field. For **keyless** setups, provide a short-lived OAuth2 `accessToken` instead of a key — SQLAnvil authenticates BigQuery with it directly (precedence: `accessToken` → `credentials` key → ADC). This suits callers that already run inside GCP and can mint a token by impersonating a service account (e.g. SQLAnvil Cloud’s runner), so no key is ever written. Tokens are short-lived (≈1h): .df-credentials.json ```json { "projectId": "my-gcp-project", "location": "US", "accessToken": "ya29...." } ``` ## Partitioning and clustering [Section titled “Partitioning and clustering”](#partitioning-and-clustering) Use `partitionBy` and `clusterBy` in the action config block: ```sql -- definitions/orders.sqlx config { type: "table", partitionBy: "DATE(created_at)", clusterBy: ["customer_id", "region"], partitionExpirationDays: 90 } SELECT order_id, customer_id, region, created_at FROM ${ref("raw_orders")} ``` Note `partitionBy` and `clusterBy` are **BigQuery-only** fields. They have no effect when targeting Postgres or Supabase. See [PostgreSQL guide](/guides/postgres/) for Postgres-native partitioning syntax. ## Incremental tables [Section titled “Incremental tables”](#incremental-tables) SQLAnvil generates a BigQuery `MERGE` statement for incremental tables: ```sql -- definitions/events_incremental.sqlx config { type: "incremental", uniqueKey: ["event_id"], partitionBy: "DATE(event_timestamp)" } SELECT event_id, event_timestamp, user_id, event_name FROM ${ref("raw_events")} ${when(incremental(), `WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM ${self()})`)} ``` ## Labels [Section titled “Labels”](#labels) Attach BigQuery labels to any action for cost tracking: ```sql config { type: "table", labels: { team: "analytics", env: "production" } } SELECT ... ``` ## Materialized views [Section titled “Materialized views”](#materialized-views) ```sql config { type: "view", materialized: true, partitionBy: "DATE(created_at)" } SELECT ... ``` Caution Materialized views in BigQuery are auto-refreshed. In Postgres, materialized views require an explicit `REFRESH MATERIALIZED VIEW` — SQLAnvil handles this via the Postgres adapter. ## BigQuery reservations [Section titled “BigQuery reservations”](#bigquery-reservations) To run actions on a specific BigQuery reservation (slot commitment): workflow\_settings.yaml ```yaml defaultReservation: projects/my-project/locations/US/reservations/my-reservation ``` Or per-action in YAML: definitions/actions.yaml ```yaml actions: - table: filename: heavy_model.sql reservation: projects/my-project/locations/US/reservations/heavy ``` ## Additional BigQuery options [Section titled “Additional BigQuery options”](#additional-bigquery-options) Pass arbitrary BigQuery table options via `additionalOptions`: ```sql config { type: "table", additionalOptions: { kms_key_name: "projects/my-proj/locations/us/keyRings/my-ring/cryptoKeys/my-key" } } SELECT ... ``` ## Assertions [Section titled “Assertions”](#assertions) SQLAnvil generates assertion views in BigQuery under your `defaultDataset`. A failing assertion (one that returns rows) is reported as an error: ```sql config { type: "assertion" } SELECT * FROM ${ref("orders")} WHERE order_total < 0 ``` # Named Environments > Run a SQLAnvil project against dev, staging, and prod with --environment — per-environment overrides and credentials, secrets kept out of committed config. Run the same project against dev, staging, and prod without juggling flags. Define each **environment** once in `workflow_settings.yaml` — its schema suffix, vars, and **its own credentials file** — and select it with `--environment ` on `compile`, `run`, or `test`. Note Requires `@sqlanvil/core` **1.7.0** or newer. Pin `sqlanvilCoreVersion: 1.7.0`. Why not just `--schema-suffix`? A schema suffix only renames output schemas — it can’t point at a different **database or credentials**. Prod and staging usually differ by more than a name. A named environment bundles the suffix *and* the connection; `--schema-suffix` stays the low-level primitive. ## Define environments [Section titled “Define environments”](#define-environments) Add an `environments:` map to `workflow_settings.yaml`. It holds only **non-secret** overrides plus a **pointer** to each environment’s credentials file — never the secrets themselves: ```yaml warehouse: postgres defaultDataset: analytics environments: dev: schemaSuffix: dev # output schemas get a _dev suffix credentials: .df-credentials.dev.json prod: defaultDatabase: prod_db vars: region: us-prod credentials: .df-credentials.prod.json ``` | Field | Effect | | ----------------- | ------------------------------------------------------------------------- | | `schemaSuffix` | Appends `_` to every output schema (same as `--schema-suffix`) | | `vars` | Variables for `${sqlanvil.projectConfig.vars.…}` (merged per-key) | | `defaultDatabase` | Overrides the default database / BigQuery project | | `defaultLocation` | Overrides the default location (BigQuery) | | `credentials` | Path (relative to the project dir) to this environment’s credentials file | Caution Each `credentials:` is just a **path** — the host/password lives in that gitignored file. `sqlanvil init` ignores `.df-credentials*.json`, so per-env files (`.df-credentials.dev.json`, etc.) stay out of version control. Nothing secret goes in the committed `workflow_settings.yaml`. ## Use an environment [Section titled “Use an environment”](#use-an-environment) ```bash sqlanvil run . --environment prod # loads prod's overrides (defaultDatabase, vars) AND its credentials file ``` `--environment` works on `compile`, `run`, and `test`. `compile` applies the config overrides; `run`/`test` additionally connect with the environment’s credentials. ## Precedence [Section titled “Precedence”](#precedence) **Explicit CLI flag > environment > `workflow_settings` defaults.** An explicit flag always wins, so you can override one piece ad hoc: ```bash sqlanvil run . --environment dev --schema-suffix qa # dev's credentials + vars, but the schema suffix is qa ``` * `vars` merge **per-key** — a `--vars` key overrides that key from the environment, which overrides `workflow_settings`; other keys are preserved. * Credentials: explicit `--credentials ` > the environment’s `credentials` > the default `.df-credentials.json`. ## Errors [Section titled “Errors”](#errors) * `--environment ` with no matching entry fails fast: `Environment "" not found. Available environments: …`. * `--environment` with no `environments:` block: `No environments defined in workflow_settings.yaml`. Note Per-environment **warehouse type** isn’t supported — `warehouse:` stays global (prod/staging differ by database/credentials, not engine). Declared sources are exempt from `schemaSuffix`, so a `dev` run reads your real sources while writing to `_dev` schemas. # File Exports > Write query results to Parquet/CSV/JSON files with type "export" — BigQuery EXPORT DATA, or Postgres/Supabase via DuckDB to s3/gs/local. A `type: "export"` action writes the result of a query to a **Parquet / CSV / JSON** file at a cloud or local location. It’s a sink: it runs after the relations its query `${ref()}`s and produces a file instead of a warehouse object. Note Requires `@sqlanvil/core` **1.8.0** or newer. Pin `sqlanvilCoreVersion: 1.8.0`. ```sql -- definitions/orders_export.sqlx config { type: "export", export: { location: "s3://my-bucket/orders/", // folder/prefix URI format: "parquet", // parquet | csv | json (json = JSONL) overwrite: true // default true } } SELECT order_id, total::text AS total FROM ${ref("orders")} ORDER BY order_id ``` ## How it runs per warehouse [Section titled “How it runs per warehouse”](#how-it-runs-per-warehouse) * **BigQuery** — compiles to native `EXPORT DATA OPTIONS(uri='gs://…/_*.parquet', …) AS