Quickstart ยท 5 minutes

Getting started

Two HTTP calls stand between your migration files and an SVG of the schema they build. This page walks through them with curl, then shows the GitHub Action and a script you can drop into any CI.

What you need

  • A directory of migration files for one of the 22 supported tools, laid out as the tool expects it.
  • curl, and jq for the polling loop (both preinstalled on macOS and on GitHub's runners). No account, no API key.

1. Pick the tool, the engine and its version

Three values go into the request: migrationType, databaseType and version. Use the values from these tables exactly as written.

ToolmigrationType
FlywayFLYWAY
LiquibaseLIQUIBASE
golang-migrateGOLANG_MIGRATE
AlembicALEMBIC
gooseGOOSE
dbmateDBMATE
AtlasATLAS
Prisma MigratePRISMA
DbUpDBUP
FluentMigratorFLUENT_MIGRATOR
Doctrine MigrationsDOCTRINE
LaravelLARAVEL
ActiveRecord (Rails)ACTIVE_RECORD
DjangoDJANGO
Drizzle ORMDRIZZLE
SqitchSQITCH
DieselDIESEL
EF CoreEF_CORE
MikroORMMIKRO_ORM
Knex.jsKNEX
SequelizeSEQUELIZE
TypeORMTYPEORM
EnginedatabaseTypeversion
PostgreSQLPOSTGRESQL10, 11, 12, 13, 14, 15, 16, 17, 18
MySQLMYSQL8.0, 8.4, 9
MariaDBMARIADB10.6, 11.4, 11.8, 12
Microsoft SQL ServerMSSQL2017, 2019, 2022, 2025
Oracle DatabaseORACLE21, 23
SQLiteSQLITElatest

Some tools do not run on every engine; the support matrix has the details. Django additionally refuses PostgreSQL 10 to 14, MySQL 8.0 and MariaDB 10.6.

2. Queue the drawing

Send the files as multipart/form-data, one files part per file. The other fields may be form fields, as below, or query parameters. The Request-Id header is a UUID you make up for this drawing: send the same request again with the same id and you get the same job back, nothing is drawn twice. userId is a UUID of your choosing that groups your drawings; pick one per project and keep it.

curl -X POST https://schemadrawer.com/api/v1/drawing \
  -H "Request-Id: $(uuidgen)" \
  -F "userId=5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f" \
  -F "migrationType=FLYWAY" \
  -F "databaseType=POSTGRESQL" \
  -F "version=18" \
  -F "files=@db/migration/V1__create_owners_and_pets.sql" \
  -F "files=@db/migration/V2__create_vets.sql" \
  -F "files=@db/migration/V3__create_visits.sql"
# {"jobId":"0b6e2f4c-8d1a-4c3e-9f7a-2a5b6c7d8e9f"}

The answer is 202 Accepted with the job id. Tools that keep migrations in nested directories, such as Prisma, Django, Sqitch or Diesel, need the relative path preserved: give each part a filename that includes the directory.

# Prisma keeps one directory per migration: send the relative path as the file name
curl -X POST https://schemadrawer.com/api/v1/drawing \
  -H "Request-Id: $(uuidgen)" \
  -F "userId=5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f" \
  -F "migrationType=PRISMA" -F "databaseType=POSTGRESQL" -F "version=18" \
  -F "files=@prisma/migrations/20240101000001_create_owners_and_pets/migration.sql;filename=20240101000001_create_owners_and_pets/migration.sql" \
  -F "files=@prisma/migrations/20240101000002_create_vets/migration.sql;filename=20240101000002_create_vets/migration.sql"

3. Poll until it is done

The job goes from PENDING to IN_PROGRESS to SUCCESS or ERROR. Poll every two or three seconds; a warm drawing takes a few seconds, a cold one up to two minutes.

curl https://schemadrawer.com/api/v1/drawing/0b6e2f4c-8d1a-4c3e-9f7a-2a5b6c7d8e9f
# {"jobId":"0b6e2f4c-...","status":"SUCCESS","createdAt":"...","errorMessage":null,"diagramUrl":"https://..."}

curl -o schema-diagram.svg "$(curl -s https://schemadrawer.com/api/v1/drawing/0b6e2f4c-8d1a-4c3e-9f7a-2a5b6c7d8e9f | jq -r .diagramUrl)"

On success the answer carries diagramUrl, a link valid for one hour:

{
  "jobId": "0b6e2f4c-8d1a-4c3e-9f7a-2a5b6c7d8e9f",
  "status": "SUCCESS",
  "createdAt": "2026-09-25T08:00:00Z",
  "errorMessage": null,
  "diagramUrl": "https://.../0b6e2f4c-8d1a-4c3e-9f7a-2a5b6c7d8e9f/diagram.svg?X-Amz-Expires=3600&..."
}

On failure it carries the migration tool's own words in errorMessage:

{
  "jobId": "7d2e9a10-4c6b-4f1e-9b3a-5e8d1c2f3a4b",
  "status": "ERROR",
  "createdAt": "2026-09-25T08:03:12Z",
  "errorMessage": "Flyway migration failed (exit code 1).\nOutput:\nFlyway OSS Edition 13.7.0 by Redgate\n...\nMigrating schema \"public\" to version \"1 - create owners\"\nErrors:\nERROR: Migration of schema \"public\" to version \"1 - create owners\" failed! Changes successfully rolled back.\nERROR: Failed to execute script V1__create_owners.sql\n----------------------------------------------\nSQL State  : 42601\nMessage    : ERROR: syntax error at or near \"last_name\"\n  Position: 100\nLocation   : /flyway/sql/V1__create_owners.sql\nLine       : 1",
  "diagramUrl": null
}

4. Keep the SVG

Download the diagram and commit it next to your migrations, then embed it where people look:

![Database schema](docs/schema-diagram.svg)

SVG scales without loss, diffs reasonably in pull requests and renders in GitHub, GitLab, Confluence, Notion and every browser.

A script for any CI

The two calls above, as a script that uploads a directory with its relative paths and waits for the result. It exits non-zero with the tool's message when a migration is broken, which makes it a useful check on its own.

#!/usr/bin/env bash
# draw.sh - upload a migration directory to schemadrawer.com and save the diagram
# usage: MIGRATION_TYPE=FLYWAY DATABASE_TYPE=POSTGRESQL VERSION=18 ./draw.sh db/migration schema.svg
set -euo pipefail
dir="$1"; out="$2"
api="https://schemadrawer.com/api/v1"
user_id="${USER_ID:-5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f}"

args=()
while IFS= read -r f; do
  args+=(-F "files=@${dir}/${f};filename=${f}")
done < <(cd "$dir" && find . -type f | sed 's#^\./##' | sort)

job=$(curl -sS -X POST "$api/drawing" \
  -H "Request-Id: $(uuidgen)" \
  -F "userId=$user_id" \
  -F "migrationType=${MIGRATION_TYPE:-FLYWAY}" \
  -F "databaseType=${DATABASE_TYPE:-POSTGRESQL}" \
  -F "version=${VERSION:-18}" \
  "${args[@]}" | jq -r .jobId)
echo "queued as $job"

while :; do
  status=$(curl -sS "$api/drawing/$job")
  case "$(jq -r .status <<<"$status")" in
    SUCCESS) curl -sS -o "$out" "$(jq -r .diagramUrl <<<"$status")"; echo "written $out"; exit 0 ;;
    ERROR)   jq -r .errorMessage <<<"$status" >&2; exit 1 ;;
  esac
  sleep 3
done

Or let the GitHub Action do it

The action runs the same flow and opens a pull request with the diagram. The minimal form assumes Flyway on PostgreSQL 18:

- uses: schemadrawercom/action@v1
  with:
    sql-directory: db/migration
    output-path: docs/schema-diagram.svg
    github-token: ${{ secrets.GITHUB_TOKEN }}

Inputs, outputs and a complete workflow are on the action reference.

Troubleshooting

SymptomMeaning
400 with "Unsupported ... version"The version is not offered for that engine; the message lists the ones that are.
400 with "Parameter 'migrationType' has an invalid value"A typo in an enum value; the message lists the accepted values.
400 with "The upload holds two files named ..."Two parts share a file name. Send relative paths as file names for nested layouts.
413The upload exceeds 100 MB. Migrations rarely do; check for data files in the directory.
ERROR with the tool's outputThe migration failed on that engine and version, exactly as it would in a deployment. Fix the migration, or pick the engine it was written for.
SUCCESS but an empty diagramThe tool applied nothing: usually file names that do not match its pattern (Flyway's V1__name.sql, for example) or an upload that lost its directory structure.
ERROR with "did not finish within 120 seconds"The drawing timed out. Try again; a first drawing with a rarely used tool or engine can be slow while its image is prepared.

Stuck? Ask.

Write to us with the job id and we will look at the drawing together.