🚀 One config, zero friction

Copy this into your repo as Jenkinsfile or let the wizard auto-detect your schema files.

// Jenkinsfile
// SchemaLens Schema Diff for Jenkins
// Place this file as Jenkinsfile in your repository root.
// Works with multibranch pipelines and PR-aware Jenkins setups.

pipeline {
    agent any

    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
    }

    environment {
        SCHEMA_PATH        = 'db/schema.sql'
        DIALECT            = 'postgres'
        FAIL_ON_BREAKING   = 'false'
        SKIP_NO_SQL_CHANGE = 'false'
        POST_BUILD_COMMENT = 'false'
        FORMAT             = 'markdown'
    }

    stages {
        stage('Schema Diff') {
            steps {
                script {
                    // Smart skip: check if any .sql files were modified
                    if (env.SKIP_NO_SQL_CHANGE == 'true') {
                        def target = env.CHANGE_TARGET ?: 'main'
                        def changed = sh(
                            script: 'git diff --name-only origin/' + target + '..HEAD 2>/dev/null || true',
                            returnStdout: true
                        ).trim()
                        if (!changed.contains('.sql')) {
                            echo '[SchemaLens] No .sql files changed — skipping.'
                            currentBuild.description = 'Skipped — no SQL changes'
                            return
                        }
                    }

                    // Ensure tools are available
                    sh 'which jq >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y -qq jq curl) || (apk add --no-cache jq curl) || true'

                    // Fetch base schema
                    def target = env.CHANGE_TARGET ?: 'main'
                    sh '''
                        git fetch origin "''' + target + '''" 2>/dev/null || true
                        git show "origin/''' + target + ''':$SCHEMA_PATH" > /tmp/schema_base.sql 2>/dev/null || echo "-- No base schema" > /tmp/schema_base.sql
                    '''

                    // Call SchemaLens API with retries
                    sh '''
                        set -euo pipefail

                        ENDPOINT="https://schemalens.tech/api/free-diff"
                        LICENSE_HEADER=""
                        if [ -n "${SL_LICENSE_KEY:-}" ]; then
                            ENDPOINT="https://schemalens.tech/api/diff"
                            LICENSE_HEADER="-H \"X-License-Key: $SL_LICENSE_KEY\""
                            echo "[SchemaLens] Using Pro endpoint."
                        fi

                        BODY=$(jq -n \
                            --arg schemaA "$(cat /tmp/schema_base.sql)" \
                            --arg schemaB "$(cat "$SCHEMA_PATH")" \
                            --arg dialect "$DIALECT" \
                            --arg format "$FORMAT" \
                            '{schemaA: $schemaA, schemaB: $schemaB, dialect: $dialect, format: $format}')

                        HTTP_STATUS=0
                        for attempt in 1 2 3; do
                            HTTP_STATUS=$(curl -s -o /tmp/schemalens_response.json -w "%{http_code}" -X POST "$ENDPOINT" \
                                -H "Content-Type: application/json" \
                                ${LICENSE_HEADER} \
                                -d "$BODY" || echo "000")
                            if [ "$HTTP_STATUS" = "200" ]; then break; fi
                            echo "[SchemaLens] Attempt $attempt failed (HTTP $HTTP_STATUS). Retrying..."
                            sleep $((attempt * 2))
                        done

                        if [ "$HTTP_STATUS" != "200" ]; then
                            echo "[SchemaLens] API failed after 3 attempts (status: $HTTP_STATUS)"
                            cat /tmp/schemalens_response.json 2>/dev/null || true
                            exit 1
                        fi

                        jq -r \'.markdown // .migrationTeaser // "No output."\' /tmp/schemalens_response.json > schema_diff_report.md
                    '''

                    // Print report to console
                    sh 'cat schema_diff_report.md'

                    // Extract metrics for build description
                    def bcount = sh(script: 'jq -r \'(.summary.breakingChangeCount // (.breakingChanges | length) // 0)\' /tmp/schemalens_response.json', returnStdout: true).trim()
                    def score  = sh(script: 'jq -r \'.riskScore.score // 0\' /tmp/schemalens_response.json', returnStdout: true).trim()
                    def label  = sh(script: 'jq -r \'.riskScore.label // "Unknown"\' /tmp/schemalens_response.json', returnStdout: true).trim()
                    def ta     = sh(script: 'jq -r \'.summary.tablesAdded // 0\' /tmp/schemalens_response.json', returnStdout: true).trim()
                    def tr     = sh(script: 'jq -r \'.summary.tablesRemoved // 0\' /tmp/schemalens_response.json', returnStdout: true).trim()
                    def tm     = sh(script: 'jq -r \'.summary.tablesModified // 0\' /tmp/schemalens_response.json', returnStdout: true).trim()

                    currentBuild.description = "Risk: ${label} (${score}/100) | +${ta} -${tr} ~${tm} | ${bcount} breaking"

                    // Post SCM comment if enabled
                    if (env.POST_BUILD_COMMENT == 'true') {
                        postScmComment()
                    }

                    // Fail on breaking changes
                    if (env.FAIL_ON_BREAKING == 'true' && bcount != '0') {
                        error("[SchemaLens] ${bcount} breaking change(s) detected.")
                    }
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'schema_diff_report.md', allowEmptyArchive: true
        }
    }
}

// Post comment to GitHub PR or GitLab MR when tokens are available
def postScmComment() {
    def report = readFile('schema_diff_report.md').trim()
    def body   = "## SchemaLens Schema Diff Report\\n\\n" + report.replace('"', '\\"').replace('\n', '\\n')
    def json   = '{"body": "' + body + '"}'

    if (env.GITHUB_TOKEN && env.CHANGE_ID) {
        def repo = env.GIT_URL.replaceFirst(/^.*github\.com[/:]([^/]+\/[^/]+)\.git$/, '$1')
        sh '''
            curl -s -X POST \
                -H "Authorization: token ''' + env.GITHUB_TOKEN + '''" \
                -H "Accept: application/vnd.github.v3+json" \
                "https://api.github.com/repos/''' + repo + '''/issues/''' + env.CHANGE_ID + '''/comments" \
                -d \'''' + json + '''\' || echo "[SchemaLens] Warning: failed to post GitHub comment."
        '''
    }

    if (env.GITLAB_TOKEN && env.CHANGE_ID) {
        sh '''
            curl -s -X POST \
                -H "PRIVATE-TOKEN: ''' + env.GITLAB_TOKEN + '''" \
                -H "Content-Type: application/json" \
                "''' + env.CI_API_V4_URL + '''/projects/''' + env.CI_PROJECT_ID + '''/merge_requests/''' + env.CHANGE_ID + '''/notes" \
                -d \'''' + json + '''\' || echo "[SchemaLens] Warning: failed to post GitLab comment."
        '''
    }
}
⚡ Generate my Jenkins config Download Jenkinsfile

📋 What your team sees in the build log

SL
SchemaLens Bot
commented 2 minutes ago

🔍 SchemaLens Schema Diff Report

🟢 Tables Added1
🔴 Tables Removed0
🟡 Tables Modified2
⚠️ Breaking Changes1
📊 Risk Score42/100 (Medium)
ALTER TABLE users
  ADD COLUMN email_verified_at TIMESTAMP;

Why Jenkins teams use SchemaLens

🛡️ Block breaking changes before merge

Dropped columns, removed indexes, and altered constraints fail the build before they reach production.

📋 Console reports

Developers see the diff summary, risk score, and migration SQL in the Jenkins console output.

📦 Artifact archiving

Every build archives a markdown report for compliance docs, audits, or offline review.

⏭️ Smart skip saves CI minutes

The stage only runs when .sql files change, so unrelated builds don't waste executor time.

Get it running in 3 steps

1

Open the Jenkins wizard

The wizard auto-detects SQL files from your public GitHub repo and selects the right dialect for your Jenkins pipeline.

2

Copy the generated config

Paste it into Jenkinsfile at the root of your repo. Adjust SCHEMA_PATH if your schema lives elsewhere.

3

Open your next pull request

SchemaLens compares the target branch schema against your branch and posts the result as a build report.

Ready to add schema diff to your Jenkins repo?

It takes 60 seconds and works with PostgreSQL, MySQL, SQLite, SQL Server, and Oracle.

⚡ Generate Jenkins config Other platforms