Apply Property substitution in Custom policy checks
In Liquibase Pro 4.31+, you can utilize Custom policy checks property substitution when setting the SCRIPT_ARGS
during check creation. This enables dynamic and context-aware scripting, such as configuring behavior based on runtime attributes like the current Git branch or target environment. It makes your Custom policy checks more flexible, reusable, and maintainable.
Prerequisites
- Understand the concept of the Checks Settings file: Use the Checks Settings Configuration File
- Review the Custom Policy Check property substitution concept content
Using property substitution in your Custom policy check
You can substitute any command argument, system property, or environment variable within the Liquibase ecosystem.

Liquibase supports the setting of properties from the following locations, from highest to lowest priority:
- Liquibase flow file stage parameters
- Liquibase flow file global parameters
- Command-line arguments
ServletConfig
initialization parametersServletContext
initialization parameters- Java system properties
- OS environment variables
- Configuration data, such as piped standard input, defaults files, and connection profiles and other properties stored in the Liquibase properties file.
For example, command-line arguments override ServletConfig
and ServletContext
initialization parameters, Java system properties, OS environment variables, and configuration data, while OS environment variables override configuration data only.
SCRIPT_ARGS
syntax used when creating a Custom Policy check:
- liquibase.properties file example:
targeturl="${liquibase.command.url}" - User supplied property:
pipelinename="${PIPELINE_NAME}" - Environment variable example:
loglevel="${LIQUIBASE_LOG_LEVEL}"
Step-by-step
Create a checks settings file
- Create a checks settings file if you don't already have one. In the CLI, run this command:
- If you don't have a checks settings file, a prompt appears that allows you to create one. Confirm the file creation in the prompt. By default, the settings file is named
liquibase.checks-settings.conf
.
liquibase checks show
Create a new custom policy check
- Create a new file in your Liquibase working directory or a subdirectory like
/scripts
. This file will contain the Python script that is your custom policy check. In this example, we title our new filecustom_check_no_tables.py
. - Open the new
custom_check_no_tables.py
file and add the following custom policy check to it:
# import Liquibase modules containing useful functions
import liquibase_utilities as lb
import sys
# define reusable variables
obj = lb.get_database_object() # database object to examine
liquibase_status = lb.get_status() # Status object of the check
if loglevel >= 2:
//Notify the user when the loglevel value is triggered
logger.info("LOG LEVEL = " + loglevel)
# write check logic
# this condition checks if the current object is a table
# and whether it starts with the prefix 'tbl_'
if lb.is_table(obj) and not obj.getName().lower().startswith("tbl_"):
# indicate that the custom check has been triggered
liquibase_status.fired = True
# set the message for Liquibase to return when check is triggered
liquibase_status.message = "Tables must start with the prefix tbl_. Add this prefix."
# halt execution of the script
sys.exit(1)
# default return code
False
Note: Liquibase will run the check against every object in the database, so this script doesn't need a Python looping mechanism to iterate through database objects.
Configure and customize your new check in the CLI
- Initiate the customization process. In the CLI, run this command:
- Give your new check a short name for easier identification. In this example, we will name the check
CustomCheckNoTables
. - Set the severity to return a code of 0–4 when triggered. In this example, we will set the severity to
1
.
These severity codes allow you to determine if the job moves forward or stops when this check triggers.
Learn more here: Use Policy Checks in Automation: Severity and Exit Code
Options: 'INFO' | 0
'MINOR' | 1
'MAJOR' | 2
'CRITICAL' | 3
'BLOCKER' | 4
- Set
SCRIPT_DESCRIPTION
. In this example, we will set the description to: - Set
SCRIPT_SCOPE
. In this example, we will set the scope todatabase
. The Python sample provided in this tutorial requires it. changelog
: for example, if your check looks for syntax patterns or attributes in your Liquibase Changelog (the changes you author in your repository). With this value, the check runs once per changeset.database
: for example, if your check looks for the presence of keys, indexes, or table name patterns in your database schema (including Liquibase Tracking Tables). With this value, the check runs once for each database object.- Set
SCRIPT_MESSAGE
. This message will display when the check is triggered. In this example we will leave this blank, as we are handling the message in the script. - Set
SCRIPT_PATH
to the location of your script. You can provide either:- a relative path, which is based on the location of the changelog file you specify with
--changelog-file
. This means that the path is relative to where that changelog file is stored.
Example: If your changelog is in a folder calledproject/
, and your script is inproject/scripts/
, the relative path to the script would bescripts/your-script.py
.
OR - An absolute path, which starts from the root of the your computer's file system and includes the full path to the script.
Example: The full path to the script would be:/Users/yourname/project/scripts/your-script.py
In this example, we will set the path to:scripts/custom_check_no_tables.py
.
- a relative path, which is based on the location of the changelog file you specify with
- Essential step for Property substitution: Set the
SCRIPT_ARGS
. This allows you to pass dynamic information into the custom policy check without modifying the Python code. With property substitution, you can reference properties such as environment variables, CLI arguments, or values from the defaults file using standard property substitution syntax.loglevel
is a variable in the Custom policy check and its value is set to the${LIQUIBASE_LOG_LEVEL}
property which can be changed dynamically in the Environment variables.
In this example, we will enterloglevel="${LIQUIBASE_LOG_LEVEL}"
- Set
REQUIRES_SNAPSHOT
. If your script scope ischangelog
, set whether the check requires a database snapshot. Specifytrue
if your check needs to inspect database objects. (If your script scope isdatabase
, Liquibase always takes a snapshot, so this prompt does not appear.)
liquibase checks customize --check-name=CustomCheckTemplate
The CLI prompts you to finish configuring your file. A message displays:
This check cannot be customized directly because one or more fields does not have a default value.
Liquibase will then create a copy of CustomCheckTemplate
and initiate the customization workflow.
Tip: For ease of use, it is a best practice to name your custom check after your Python script.
This script looks to see if any tables exist and notifies you if one is detected.
In general, you should set the scope to changelog
or database
depending on what your custom script does:
Tip: It is a best practice for your custom checks to have only one scope, not both scopes.
Note: The larger your database, the more performance impact a snapshot causes. If you cannot run a snapshot due to memory limitations, see Memory Limits of Inspecting Large Schemas.
You have now successfully created and customized a policy check!
Customization complete. Review the table below to confirm your changes.
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
| Short Name | Scope | Type | Status | Severity | Customization | Description |
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
| CustomCheckNoTables | changelog | sql, | disabled | 0 | SCRIPT_DESCRIPTION = Custom | Executes a custom check |
| | | xml, | | | check | script. |
| | | yaml, | | | SCRIPT_SCOPE = CHANGELOG | |
| | | json | | | SCRIPT_MESSAGE = The message | |
| | | | | | to display when the check is | |
| | | | | | triggered | |
| | | | | | SCRIPT_TYPE = PYTHON | |
| | | | | | SCRIPT_PATH = null | |
| | | | | | SCRIPT_ARGS = null | |
| | | | | | REQUIRES_SNAPSHOT = false | |
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
| MyCustomCheck | changelog | sql, | enabled | 0 | SCRIPT_DESCRIPTION = Custom | Executes a custom check |
| | | xml, | | | check | script. |
| | | yaml, | | | SCRIPT_SCOPE = CHANGELOG | |
| | | json | | | SCRIPT_MESSAGE = The message | |
| | | | | | to display when the check is | |
| | | | | | triggered | |
| | | | | | SCRIPT_TYPE = PYTHON | |
| | | | | | SCRIPT_PATH = python_script.py | |
| | | | | | | |
| | | | | | SCRIPT_ARGS = | |
| | | | | | logfile="${LIQUIBASE_LOG_LEVEL | |
| | | | | | }" | |
| | | | | | REQUIRES_SNAPSHOT = false | |
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
Liquibase command 'checks copy' was executed successfully.
Note: If the content of ${property-name}
does not match a property, it is left as-is and it is not removed. The supported format includes alphanumeric characters, +
, -
, .
, and _
.
If a Custom Policy Check calls on a property that is not defined, you will get an error:Error while executing script 'test.py': Exception: No value found for argument 'test' line: 13
Next steps
- Run a Custom Policy Check
- Troubleshooting a scope mismatch attribute error: Error while executing script 'custom-check-no-tables.py': AttributeError: 'NoneType' object has no attribute 'getObjectTypeName' line: 7
- Sample Custom Policy Check Scripts: see examples of real-world scripts for Custom policy checks that you can adapt
- Liquibase Python Modules: learn how to access the Liquibase API to write more custom checks
- Write Dynamic Status Messages for Custom Policy Checks: improve the usability of your checks with better output messages