For Databricks Unity Catalog

Unity Catalog tables, changed like code.

A schema change should be a reviewed, versioned change applied by automation, not a hand edit or a CREATE TABLE buried in a notebook. ucat keeps your Databricks managed table definitions in git as YAML, compares them with what is really in Unity Catalog, shows exactly what would change, and applies it from CI, the same way through dev, test and prod.

Declare. Plan. Apply.

Cato, the ucat mascot, says: No changes.

The missing piece next to Declarative Automation Bundles (formerly Databricks Asset Bundles). Describe your tables declaratively, review the plan in a pull request, and apply the same definitions to dev, test and prod, one target at a time.

Four pieces of a Databricks project as codeA puzzle of four pieces. Jobs, pipelines, and apps, dashboards and AI are deployed by Declarative Automation Bundles. The fourth piece, managed tables, is added by ucat.JobsPipelinesSDP, formerly DLTApps, dashboards, AIManaged tablesucat
Bundles deploy your jobs, pipelines and apps. Managed tables are not one of their resources: ucat adds them, in the same way.

Declarative Automation Bundlesucat

Who owns the table

In a pipeline, the pipeline owns the table: its schema comes from the query, and the table lives and changes with that code. With ucat the table comes first. Its definition is a file in git that you review like a contract, and the jobs, pipelines and notebooks that fill it are just the ones that write into it.

ucat does not write data into the table and does not check the quality of the data. “Contract” describes the process: a schema change goes through a pull request like any other change. It is not a guarantee about the data. More in Concepts.

Terminal recording: ucat validate, plan and apply create a table; then a new column, a different clustering and a table property are added to it (changed lines highlighted), with validate, plan and apply; the last plan prints No changes.

Sound familiar?

  • A column was added in production by hand. Dev and test never got it.
  • Your jobs are full of CREATE TABLE IF NOT EXISTS. Each pipeline ends up owning its own table definition, so table creation is scattered across the code. It works, but it is a workaround for a missing tool.
  • Nobody can review a table change before it reaches production.
  • A rename quietly became a drop and a create.

How it works

Cato reading a plan
  1. Describe tables in YAML

    Describe the table you want. One file per table, kept in git next to your code.
    name: orders
    schema: silver
    comment: "Customer orders"
    columns:
      - name: id
        type: BIGINT
        nullable: false
        comment: "Primary key"
      - name: created_at
        type: TIMESTAMP
    clustering:
      columns: [id]
  2. Plan

    ucat reads the live state from the Unity Catalog REST API and shows exactly what would change. There is no state file to store or lock.
    $ ucat plan -t dev
      `main`.`silver`.`orders`
        + create table `main`.`silver`.`orders` (2 columns)
    
    Plan: 1 to add, 0 to change, 0 to destroy.
  3. Apply

    Review, then apply. Destructive changes ask for confirmation; CI uses --auto-approve.
    ucat apply -t dev
  4. Change it later

    Edit the YAML: add a column, change the clustering, set a table property. Run plan and apply again; ucat changes the table in place.
      - name: discount
        type: DECIMAL(5,2)
    clustering:
      columns: [customer_id]
    tblproperties:
      delta.enableChangeDataFeed: "true"
    $ ucat plan -t dev
      `main`.`silver`.`orders`
        ~ set 1 tblproperties
        ~ add column "discount" DECIMAL(5,2)
        ~ set clustering by (customer_id)
    
    Plan: 0 to add, 3 to change, 0 to destroy.

Key features

Schema changes get the same treatment as code: versioned, reviewed, applied by automation.

Describe it, don't script it

You write the table you want. ucat works out the CREATE and ALTER statements, in the right order. No migrations to hand-write.

columns:
  - name: id
    type: BIGINT
  - name: discount
    type: DECIMAL(5,2)
clustering:
  columns: [customer_id]

Reviewed before it runs

ucat plan shows exactly what would change, with --show-sql for the statements. Put it in a pull request.

$ ucat plan -t dev
  `main`.`shop`.`orders`
    ~ set 1 tblproperties
    ~ add column "discount" DECIMAL(5,2)
    ~ set clustering by (customer_id)

Plan: 0 to add, 3 to change, 0 to destroy.

Safe by default

A rename stays a rename (rename_from), never a drop and a create. A column drop without column mapping is refused at planning time.

rename_from: order_items

$ ucat plan -t dev
  `main`.`shop`.`order_lines`
    ~ rename table order_items → order_lines

Plan: 0 to add, 1 to change, 0 to destroy.

Same definitions, every environment

One YAML, a target per environment. CI applies the same definitions to dev, test and prod, one target at a time. Each target can point to its own workspace.

Built for CI/CD

Exit codes, --detailed-exitcode, --json. One binary for Linux, macOS and Windows.

Start from what you have

ucat pull writes YAML from tables that already exist.

How it compares

ucat does one thing: it keeps the definitions of standalone managed tables in git and brings Unity Catalog to that state. The other tools do other jobs; several of them work well next to ucat.

Where the tools fitA grid of two columns and three rows. Left to right: you write the steps (imperative) to you write the result (declarative). Bottom to top: specialist to all-in-one. Declarative Automation Bundles (DAB) and Terraform are declarative and all-in-one, in the top right cell. dbt is declarative and in the middle row. Notebooks are imperative and in the middle row. Liquibase is imperative and a specialist, in the bottom left cell. ucat is declarative and a specialist, in the bottom right cell. Shape and colour show what the tool keeps besides your files: a green circle for nothing (ucat, dbt, notebooks), a brown square for a history table (Liquibase), a purple diamond for a state file and a backend (DAB, Terraform).IMPERATIVEDECLARATIVEHow a change is describedALL-IN-ONESPECIALISTHow much one tool coversDAB: Declarative Automation Bundles (also called Bundles): describes the result in YAML and deploys many resource types.DABTerraform: Describes the result; covers many platforms and resource types.Terraformdbt: Models are declarative SELECTs; broad within data transformation: tests, docs, lineage, snapshots.dbtNotebooks: You write the steps in code; can do almost anything, so it sits in the middle for scope.NotebooksLiquibase: You write changesets, the steps of each change; one job: schema changes of a database.Liquibaseucat: A table file describes the result; one job: managed tables of Unity Catalog.ucat

Hover over or focus a tool to see why it sits where it does.

What you keep besides your filesNothingA history tableA state file and a backend

Left to right: from scripts and migrations you write to a desired state the tool works out. Bottom to top: from one job done in depth to many jobs in one tool.

Our reading of each tool's documentation on 14 November 2026. Positions are approximate: a tool sits in the cell it fits best, and where it stands inside a cell means nothing. Terraform, for example, can also manage individual tables.

See the table · Disagree with a position? Open an issue.

CriterionucatTerraformBundles + LakeflowDeclarative Automation Bundles and Lakeflow pipelinesdbtAtlasLiquibaseNotebooksCREATE TABLE IF NOT EXISTS
Designed forDefinitions of standalone managed tablesInfrastructure as code, including tables, schemas, catalogsDeploying and running pipelines and jobs; pipeline-owned materialized views and streaming tablesData transformations; a table is the output of a modelDatabase schema management, declarative or versionedVersioned database schema changes, applied as migrationsRunning code; creating a table is a side job
How you describe a tableDeclarative YAML, one file per tableDeclarative HCL resourcePipeline code (SQL or Python)SQL model plus YAML propertiesSQL schema fileChangelog of changesets (SQL, YAML, XML or JSON)Notebook code (SQL or Python)
What you runOne binaryTerraform CLI, provider, state fileDatabricks CLI; a pipeline in the workspacePython, dbt-databricks adapterAtlas CLI, Atlas Pro account for DatabricksLiquibase and its Databricks extension; needs Java (or a Docker image); a history table in each databaseNothing extra
Licence and costOpen source (Apache 2.0), freeProvider: Apache 2.0. Terraform itself: Business Source LicensePart of Databricksdbt Core open source (Apache 2.0), freeDatabricks needs the paid Pro planCommunity: Functional Source License (source available; each release becomes Apache 2.0 after two years). Liquibase Secure: commercialPart of Databricks
Supports managed tables●●○●
Works out the change and applies it in place (ALTER)●●○
Shows a plan before applying●○●○

yes partly (see the note) no. Checked against the vendors' documentation and, for the Terraform provider, its source code (v1.142.0), on 14 November 2026. The Liquibase column was checked against its official documentation on that date.

What ucat adds on top

Explicit renames with rename_from. Checks while planning, before any DDL runs: an unsupported column type change, a new NOT NULL column, a column drop on a table without column mapping. ucat pull, which writes YAML from tables that already exist.

Use together

  • Terraform manages catalogs, schemas and grants; ucat manages the tables inside them.
  • Bundles deploy your pipelines; ucat manages the standalone tables around them, for example the tables your jobs load data into.
  • dbt transforms downstream; ucat keeps the definitions of the tables you load into.

When ucat is not a fit

  • You need materialized views or streaming tables owned by a pipeline.
  • You need more than managed tables. See the Limitations (the project is actively developing; the Roadmap shows what is next).
What each tool is for, and where ucat fits

Terraform manages infrastructure, tables included, through the Databricks provider. It keeps a state file and runs the SQL on a cluster or a SQL warehouse. The provider documentation itself warns that it "doesn't handle complex cases of schema evolution" and recommends specialised tools such as Liquibase and Flyway, which run versioned migrations. ucat is a specialised tool for Unity Catalog tables that is declarative instead: no state file, a plan with the statements, no Terraform workflow for a schema change.

Liquibase runs versioned migrations: you write each change as a changeset, and every database keeps a history table of what was applied. That is strong for migrations of data, a fixed order of changes and many kinds of databases, and it needs Java (or the Docker image). ucat is declarative: the YAML says what the table should look like, ucat compares it with Unity Catalog and works out the ALTER statements, so a change made by hand shows up in the next plan. There is no history table; git and the plan are the record. ucat is one binary without Java, and the plan with its SQL fits in a pull request. Liquibase can also show pending SQL (update-sql) and compare two databases (diff), but it does not compare a changelog with a live table.

Declarative Automation Bundles deploy pipelines, jobs and other resources, among them catalogs, schemas, volumes and registered models. Tables are not a bundle resource type. Deployments use the direct engine, which keeps its own state file (the Terraform engine was removed in CLI 1.20.0). ucat deploys the standalone tables next to the pipelines that bundles deploy.

Lakeflow pipelines build materialized views and streaming tables. While a pipeline is active, it manages the refresh schedule and permissions of its tables: the table and its pipeline are one unit. This is a different ecosystem from standalone managed tables. If your table is produced by such a pipeline, it is not a job for ucat.

dbt transforms data. A table is the output of a model, so its schema follows the model's SQL, and dbt manages only the tables it builds. ucat is for the tables dbt does not build, for example the tables you load data into, and for keeping their definitions reviewed in git.

Atlas is a schema management tool with a declarative mode and Databricks support. Databricks is available only on its Pro plan or during a trial, and the schema is described in SQL. ucat is open source, uses YAML and is built only for Unity Catalog tables.

Notebooks with CREATE TABLE IF NOT EXISTS are the common baseline, and in our view a workaround for a missing tool. Software engineering settled this long ago: a schema change is a versioned migration applied by automation, kept apart from the code that uses the table, and data teams now keep contracts as code in Git instead of scattering them across jobs. For standalone Unity Catalog tables, nothing in the matrix above did that job. ucat does.

Products change: if something here is out of date, please open an issue. All product names and trademarks are the property of their respective owners.

Quickstart

From install to the first plan in about five minutes.

  1. Install

    With Homebrew on macOS or Linux, or download a binary from GitHub Releases (Linux, macOS, Windows). Go 1.26 or newer can build it from source.
    brew install ninilich/ucat/ucat
    ucat version
  2. Authenticate

    Log in the usual Databricks way. ucat uses the workspace host of the target and the login you made for that host.
    databricks auth login --host https://<workspace>.cloud.databricks.com
  3. Create a project

    ucat init
  4. See what would change

    ucat plan -t dev
  5. Apply

    ucat apply -t dev
    Cato about to press the apply button

Already have tables in Unity Catalog? Import them: ucat pull -t dev writes the YAML for you.

Want to try it on a ready project? The quickstart example creates a small shop schema, changes it twice and cleans up.

Full getting-started guide