Manual schema reviews don't scale. When your team ships ten migrations a week, asking a human to read every ALTER TABLE statement is a bottleneck. Worse, it's unreliable โ reviewers miss things, especially at 5 PM on a Friday.
Automation doesn't replace judgment. It removes tedium and catches the obvious dangers before a human ever sees them. The ideal workflow looks like this:
This post shows you how to build that workflow using the SchemaLens API, standard webhooks, and a little glue code.
SchemaLens exposes a single REST endpoint at POST /api/diff. It accepts two schema strings, a dialect, and returns a structured diff plus generated migration SQL.
curl -X POST https://schemalens.tech/api/diff \
-H "Content-Type: application/json" \
-d '{
"schemaA": "CREATE TABLE users (id SERIAL PRIMARY KEY);",
"schemaB": "CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255));",
"dialect": "postgres",
"format": "json"
}'
The response includes:
diff โ added/removed/modified tables, columns, constraints, indexes, triggers, views, enumsmigration โ ready-to-run ALTER TABLE DDLbreakingChanges โ array of dangerous changes with severity labelssummary โ counts of all object types for quick scanningThere's also a markdown format that returns a full human-readable report. We'll use both.
Rate limits: The API allows 30 requests per minute per IP. For CI/CD use, that's generous. If you need more, cache results or run the CLI locally.
The most impactful automation is also the simplest: notify your team when a dangerous schema change is introduced.
Here's a Node.js script you can run in CI or on a schedule. It diffs two schemas and posts to a Slack webhook only if breaking changes are found:
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
async function notifySlack(schemaA, schemaB, dialect = 'postgres') {
const res = await fetch('https://schemalens.tech/api/diff', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ schemaA, schemaB, dialect, format: 'json' })
});
const data = await res.json();
if (!data.breakingChanges || data.breakingChanges.length === 0) {
console.log('No breaking changes. Silence is golden.');
return;
}
const critical = data.breakingChanges.filter(b => b.severity === 'critical');
const warnings = data.breakingChanges.filter(b => b.severity === 'warning');
const blocks = [
{
type: 'header',
text: { type: 'plain_text', text: '๐จ Breaking schema changes detected' }
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Critical:* ${critical.length}\n*Warnings:* ${warnings.length}\n\nReview the migration before deploying.`
}
}
];
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ blocks })
});
}
Run this in your CI pipeline after generating a migration. If breaking changes exist, your #engineering channel gets a ping. If not, nothing happens โ no notification fatigue.
Slack alerts are great for attention. PR comments are great for context. Here's how to post a schema diff directly into every migration PR.
Using GitHub Actions + the SchemaLens API:
# .github/workflows/schema-diff-comment.yml
name: Schema Diff Comment
on:
pull_request:
paths:
- 'db/schema.sql'
- 'migrations/*.sql'
jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get base schema
run: git show origin/${{ github.base_ref }}:db/schema.sql > base-schema.sql
- name: Diff schemas
id: diff
run: |
curl -s -X POST https://schemalens.tech/api/diff \
-H "Content-Type: application/json" \
-d "{\"schemaA\":$(jq -Rs . < base-schema.sql),\"schemaB\":$(jq -Rs . < db/schema.sql),\"dialect\":\"postgres\",\"format\":\"markdown\"}" \
| jq -r '.markdown' > report.md
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = '## Schema Diff Report\n\n' + fs.readFileSync('report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});
Every PR that touches schema files gets an automatic comment with the full diff. Reviewers see added columns, dropped indexes, and breaking changes without leaving GitHub.
Production schemas drift. Someone runs a hotfix migration manually. A DBA tweaks an index. A feature flag experiment adds a temporary column. Over weeks, your codebase schema and production schema diverge.
Nightly drift detection catches this before it becomes a deployment blocker:
# Run this nightly via cron or GitHub Actions schedule
const fs = require('fs');
async function checkDrift() {
// Export production schema however you normally would
const prodSchema = await exportProductionSchema();
const codeSchema = fs.readFileSync('db/schema.sql', 'utf8');
const res = await fetch('https://schemalens.tech/api/diff', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
schemaA: codeSchema,
schemaB: prodSchema,
dialect: 'postgres',
format: 'json'
})
});
const data = await res.json();
const hasChanges =
data.diff.tablesAdded.length > 0 ||
data.diff.tablesRemoved.length > 0 ||
data.diff.tablesModified.length > 0;
if (hasChanges) {
await notifySlack(`Schema drift detected. ${data.diff.tablesModified.length} tables modified.`);
process.exit(1); // Fail the job for visibility
}
}
checkDrift();
Schedule this with GitHub Actions on: schedule: - cron: '0 6 * * *' or a simple cron job. If drift is detected, your team knows within 24 hours โ not three months later when a deploy explodes.
If you want real-time schema diffing integrated into your own platform, build a lightweight webhook listener that calls the SchemaLens API.
Example: a Vercel serverless function that receives a webhook from your database platform (like Supabase or PlanetScale) and runs a diff:
// api/schema-webhook.js
module.exports = async (req, res) => {
const { schemaA, schemaB, dialect, callbackUrl } = req.body;
const diffRes = await fetch('https://schemalens.tech/api/diff', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ schemaA, schemaB, dialect, format: 'json' })
});
const result = await diffRes.json();
// Post result back to your system
await fetch(callbackUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
breakingCount: result.breakingChanges.length,
summary: result.summary,
migration: result.migration
})
});
res.status(200).json({ processed: true });
};
This pattern turns any event โ a schema push, a migration deployment, a scheduled job โ into a trigger for automated diff analysis.
Schema structures can reveal sensitive information: table names like patients or credit_cards, column names like ssn or salary. When automating schema reviews, keep these principles in mind:
Here's what a fully automated schema review pipeline looks like with SchemaLens:
/api/diff and posts a markdown report as a PR comment.db/schema.sql against production to catch drift.Total human time per migration: 2 minutes of review. Total automation setup: 30 minutes once. Total incidents prevented: hard to measure, but the teams that implement this stop having 3 AM pages from schema issues.
SchemaLens is free for up to 10 tables. Use the API, CLI, or browser app to diff schemas and generate migrations instantly.
Try SchemaLens FreeRelated reading: