---
title: "Adopt an existing database"
description: "Bring a database under atlantis by generating declarations from live tables, reviewing and committing them, and baselining without running DDL."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.tryatlantis.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Adopt an existing database

Bring a database full of existing tables under atlantis: generate
declarations from what is live, review and commit them, and baseline —
recording the schema without running any DDL against your tables.

> **Prerequisites**
>
> - Your organisation is already pointed at the database (done during
> onboarding); the connection string is sealed and never read back out.
> - The `admin` role, for the console import's plan and apply steps; the
> apply also re-authenticates you.
> - A service repository with `tide.yaml`, for the declarations to live in.

## How the pieces fit

atlantis creates its own entity tables at `atlantis.<namespace>_<entity>`
by default, with the entity name snake-cased — `entity VendorImport in
vendor` is `atlantis.vendor_vendor_import`. Existing tables live elsewhere, so each adopted entity carries
the `table "<schema.table>"` modifier pointing at its physical table:

```atl
entity Account in consumer {
  table "consumer.accounts"

  id            varchar(8) primary
  email         varchar(255) not null unique
  created_at    timestamptz not null default now()
  deleted_at    timestamptz

  soft_delete by deleted_at
}
```

Field names match the live column names byte for byte, and each
`table "..."` value is unique across every declared entity — two entities
claiming one physical table is rejected at `tide plan`.

## 1. Generate declarations

Two routes to the same files:

**From the console.** The Schema page's onboarding offers **Import a
database**: the console reads the database read-only and opens a review
page — every discovered table as a generated entity, suggested tightenings
(missing primary keys, unindexed foreign keys, tenant-isolation
candidates), and notes on what introspection could not verify.

**From the terminal.** In a repository with no `.atl` files yet,
`tide inspect --generate` writes one `.atl` file per undeclared table,
reading columns, types, keys, defaults, and foreign keys from the
catalogue:

```bash
tide inspect --generate=schema/ --schemas=consumer,vendor
```

Entity names are derived from table names, so `user_accounts` becomes
`UserAccounts`. Renaming one after adoption is a breaking change, so
edit the files before committing. Skipped tables are reported with a
reason, existing files are never overwritten, and warnings about what
introspection could not verify print with the run; inside a generated
file, a comment block lists any column whose Postgres type has no `.atl`
spelling.

## 2. Review and commit

The generated declarations understate a schema: introspection does not
read `check` predicates or every index shape back. Read each file, tighten
what you know — then commit them to the repository at the paths
`tide.yaml` lists.

## 3. Baseline

The baseline happens in the console. On the import's review page,
**Commit** computes the plan, and **Apply**, behind a re-authentication,
records the declarations as the baseline. A baseline records the
checkpoint; it runs no DDL against your tables. It refuses when a
declaration and the database **disagree** about something that exists on
both sides, naming each mismatch; tables declared but absent, or present
but undeclared, are reported and do not block.

Then register each caller against the baseline:

```bash
tide plan
tide apply
```

With the checkpoint recorded and the declarations matching it, the plan is
zero changes and the apply is a metadata write: the caller's files are
recorded and the checkpoint moves. No DDL runs. Do not run `tide apply`
before the console baseline — with no checkpoint recorded, the plan is a
full set of `CREATE` statements for tables that already exist.

### Legacy unique indexes can block apply

A zero-change plan does not guarantee a clean apply. atlantis declares
uniqueness as a Postgres `UNIQUE` constraint; a legacy database often
carries the same uniqueness as a bare `CREATE UNIQUE INDEX` with no
backing constraint, which never appears in the DDL diff but rejects
writes the declared schema allows. `tide apply` refuses to proceed over
one, printing the live index name and the remediations:

- Declare the uniqueness in the `.atl` so atlantis owns it — `unique` on
  the field or `unique by a, b` for a non-partial index (classified
  backfill-required), or `unique index partial by <cols> where <pred>` for
  a partial one (predicates are normalised through Postgres before
  comparing, so casts and operand order don't matter).
- Drop the index from the database, if it is unwanted.

`tide plan` only warns about index drift, and only in `--format=json`
(`index_drift`, `index_drift_notes`, `index_drift_error`).

## 4. Verify

Run `tide plan` again — it reports zero changes — and issue a read through
the generated typed client, confirming it returns the rows direct SQL
sees.

## 5. Cut over

Move your application package by package:
flag-gate the atlantis client beside the existing database code and flip
per package — both paths read and write the same physical tables, so the
flip is a routing change, not a data change.

## Common errors

- `<entity>: invalid table name "<value>"` — the `table "..."` value has an
  invalid identifier (embedded space, leading digit, multiple dots). Use a
  simple `[schema.]table` name.
- `table "<name>" is claimed by both <A> and <B>` — two entities mapped to
  one physical table; each value must be unique across the merged schema.
- A `cross_caller_breaking` plan with `table override changed: "<old>" ->
  "<new>"` — the modifier value moved relative to the applied schema.
  atlantis does not auto-rename; rename the physical table first.

## Related

- [DSL grammar reference](/reference/dsl-grammar/#entity-level-clauses) — the `table` modifier alongside other entity-body clauses.
- [`tide inspect`](/reference/cli-tide/#tide-inspect) — the drift report and `--generate`.
- [The apply path](/concepts/how-atlantis-runs-your-schema/) — the checkpoint a baseline records.

Source: https://docs.tryatlantis.dev/guides/adopt-an-existing-database/index.mdx
