liquibase-migration
FOLIO Liquibase Migration Guidelines
1. Folder Structure
Preferred Layout
The preferred structure is versioned — each release gets its own folder and aggregator file:
src/main/resources/db/changelog/
├── changelog-master.xml
└── changes/
├── changelog-v1.0.xml
├── v1.0/
│ ├── create-table-foo.xml
│ └── add-index-foo-bar.xml
├── changelog-v2.0.xml
└── v2.0/
└── alter-table-foo-add-column-baz.xml
Preferred structure rules:
changelog-master.xmlincludes only version aggregators (changes/changelog-vX.Y.xml)- Each
changelog-vX.Y.xmlincludes only files fromchanges/vX.Y/ - Create a new
vX.Y/directory andchangelog-vX.Y.xmlfor each module release - All
<include>elements userelativeToChangelogFile="true"
changelog-master.xml example:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<include file="/changes/changelog-v1.0.xml" relativeToChangelogFile="true"/>
<include file="/changes/changelog-v2.0.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
changes/changelog-v2.0.xml example:
<databaseChangeLog ...>
<include file="/changes/v2.0/create-table-foo.xml" relativeToChangelogFile="true"/>
<include file="/changes/v2.0/alter-table-foo-add-column-bar.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
Working with Existing Modules
Before creating new files, check the existing changelog structure in the module. Always follow the layout already in use — do not reorganize or introduce a different structure mid-project.
- If the module uses
changelog-master.xmlwith per-version aggregators → follow that pattern - If the module uses a flat
changelog-master.xmlwith direct<include>entries → continue adding entries directly there - If the module uses different naming or nesting → match what is already there
When starting a brand new module with no existing migrations, use the preferred structure above.
2. Changeset ID Naming Convention
Format
<JIRA-TICKET>@@<scope>-<action>-<target>[-<detail>]
Allowed Values
| Part | Values |
|---|---|
| scope | schema, data, refactor, fix |
| action | create, alter, drop, rename, migrate, backfill, seed, cleanup, enforce |
| target | table-<name>, column-<table>-<col>, index-<name>, fk-<from>-to-<to>, type-<name> |
| detail | (optional) specific intent, e.g. add-not-null, add-metadata-fields |
Style Rules
- Lowercase only
- Kebab-case only (no underscores, no spaces)
- ASCII only — no
:characters - Use imperative verbs (not gerunds or past tense)
- IDs are immutable after release — never rename a released changeset ID
✅ Good Examples
MYMOD-101@@schema-create-table-item
MYMOD-102@@schema-alter-table-item-add-column-status
MYMOD-103@@schema-create-index-item-status
MYMOD-104@@schema-create-fk-item-to-location
MYMOD-105@@data-seed-item-default-status
MYMOD-106@@data-migrate-rule-to-specification-rule-metadata-fields
MYMOD-107@@schema-enforce-column-item-id-not-null
MYMOD-108@@schema-create-type-status-enum
❌ Bad Examples
MYMOD-101@@CreateItemTable # PascalCase, no scope/action
MYMOD-101@@schema:create:table # colons not allowed
MYMOD-101@@schema-creating-table # gerund
MYMOD-101@@schema-created-table # past tense
item-table # missing JIRA ticket and @@
3. Changeset Structure
Every changeset must include:
<comment>— describes what the changeset does and why
<changeSet id="MYMOD-42@@schema-create-table-item" author="john_doe">
<comment>Create item table for inventory storage</comment>
<createTable tableName="item">
...
</createTable>
</changeSet>
4. FOLIO Standard Column Types
Use these types consistently across all FOLIO modules:
| Purpose | Type |
|---|---|
| Primary key / foreign key (UUID) | uuid |
| Short string | varchar(36) – varchar(250) |
| Long string | varchar(2500) or text |
| JSON blob | jsonb |
| Timestamp | datetime |
| Boolean flag | bool |
| Version counter (optimistic locking) | integer |
| Single character code | char |
Metadata Columns
Before adding metadata columns to a new table, ask the user whether they are needed. Not all tables require audit tracking.
If the user confirms, include:
<column name="created_date" type="DATETIME">
<constraints nullable="false"/>
</column>
<column name="updated_date" type="DATETIME">
<constraints nullable="false"/>
</column>
<column name="created_by_user_id" type="uuid">
<constraints nullable="false"/>
</column>
<column name="updated_by_user_id" type="uuid">
<constraints nullable="false"/>
</column>
5. Common Change Patterns
Create Table
<changeSet id="MYMOD-10@@schema-create-table-item" author="jane_doe">
<comment>Create item table</comment>
<createTable tableName="item">
<column name="id" type="UUID">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_item"/>
</column>
<column name="barcode" type="varchar(250)"/>
<column name="status" type="varchar(36)">
<constraints nullable="false"/>
</column>
<column name="data" type="jsonb"/>
<column name="_version" type="integer"/>
<column name="created_date" type="DATETIME"><constraints nullable="false"/></column>
<column name="updated_date" type="DATETIME"><constraints nullable="false"/></column>
<column name="created_by_user_id" type="uuid"><constraints nullable="false"/></column>
<column name="updated_by_user_id" type="uuid"><constraints nullable="false"/></column>
</createTable>
</changeSet>
Add Foreign Key
<changeSet id="MYMOD-10@@schema-create-fk-item-to-location" author="jane_doe">
<comment>Add FK from item to location</comment>
<addForeignKeyConstraint
baseTableName="item"
baseColumnNames="location_id"
referencedTableName="location"
referencedColumnNames="id"
constraintName="fk_item_location_id"/>
</changeSet>
Create Index
<changeSet id="MYMOD-11@@schema-create-index-item-status" author="jane_doe">
<comment>Create B-tree index on item.status for lookup performance</comment>
<createIndex tableName="item" indexName="idx_item_status">
<column name="status"/>
</createIndex>
</changeSet>
Add Column
<changeSet id="MYMOD-15@@schema-alter-table-item-add-column-effective-location-id" author="jane_doe">
<comment>Add effective_location_id column to item table</comment>
<addColumn tableName="item">
<column name="effective_location_id" type="UUID"/>
</addColumn>
</changeSet>
Data Migration / Seed
<changeSet id="MYMOD-20@@data-seed-item-default-status" author="jane_doe">
<comment>Set default status 'Available' for items with null status</comment>
<sql>
UPDATE item
SET status = 'Available'
WHERE status IS NULL;
</sql>
</changeSet>
6. Anti-Patterns to Avoid
❌ Missing comment — no context for reviewers or future developers
<!-- BAD -->
<changeSet id="MYMOD-2@@schema-alter-table-item-add-column-bar" author="foo">
<addColumn tableName="item">...</addColumn>
</changeSet>
❌ Renaming a released changeset ID — breaks Liquibase checksum validation in all deployed environments
❌ Multiple unrelated changes in one changeset — makes rollback and debugging harder; one logical change per changeset
❌ Using runOnChange="true" on schema changesets — only acceptable for stored procedures/views, never for DDL
More from folio-org/folio-eureka-ai-dev
write-user-story
Use when creating, writing, or refining a user story or ticket. Produces structured stories with purpose/overview, functional requirements, Given-When-Then acceptance criteria, and manual testing guidance. Also use when asked to define acceptance criteria, scope a feature, or prepare a story for development.
21document-feature
Use when the user asks to document an implemented feature. Analyze the diff from the base branch, infer the feature boundary and name, and generate behavioral feature documentation under docs/features/.
19unit-testing
Use when writing or reviewing Java unit tests. Enforces Mockito/JUnit 5 best practices - strict stubbing, no lenient mode, specific matchers, complete flow stubbing, Arrange-Act-Assert structure, and clear test naming.
16code-review
Use when the user asks to perform a code review, review code changes, analyze a diff, or audit code quality. Runs a structured review of git diff output covering security, correctness, performance, maintainability, and style. Produces a markdown report saved as a .md file named after the current branch.
12skill-feedback
Use when a user has finished using one installed skill and wants to preserve actionable feedback about that skill while the session context is still fresh
4write-bug
Use when creating, writing, or refining a bug report for a FOLIO project. Produces structured bug tickets with a clear summary, preconditions, numbered steps to reproduce, expected vs. actual results, and supporting evidence (logs, stack traces, screenshots). Also use when asked to file a defect, triage an issue, or prepare a bug for Jira. Optionally interacts with the user to gather missing context and can create the ticket via the Jira MCP integration.
4