Pkl 0.31 Release Notes
Pkl 0.31 was released on February 26th, 2026.
The latest bugfix release is 0.31.0. (All Versions)
The next release (0.32) is scheduled for July 2026. To see what’s coming in the future, follow the Pkl Roadmap.
Please send feedback and questions to GitHub Discussions, or submit an issue on GitHub.
Pkl is hosted on GitHub. To get started, follow Installation.
Highlights 💖
Power Assertions
Pkl has two places that are effectively assertions. These are:
-
Type constraint expressions
-
Test facts
Currently, when these assertions fail, the error message prints the assertion’s source section. However, this information can sometimes be lacking. It tells you what the mechanics of the assertion is, but doesn’t tell you why the assertion is failing.
For example, here is the current error output of a failing typecheck.
–– Pkl Error ––
Type constraint `name.endsWith(lastName)` violated.
Value: new Person { name = "Bub Johnson" }
7 | passenger: Person(name.endsWith(lastName)) = new { name = "Bub Johnson" }
Just from this error message, we don’t know what the last name is supposed to be.
What is name supposed to end with?
We just know it’s some property called lastName but, we don’t know what it is.
In Pkl 0.31, the error output becomes:
–– Pkl Error ––
Type constraint `name.endsWith(lastName)` violated.
Value: new Person { name = "Bub Johnson" }
name.endsWith(lastName)
│ │ │
│ false "Smith"
"Bub Johnson"
7 | passenger: Person(name.endsWith(lastName)) = new { name = "Bub Johnson" }
Now, we know what the expectation is.
This type of diagram is also added to test facts. When tests fail, Pkl emits a diagram of the expression, and the values produced.
For example, given the following test:
amends "pkl:test"
facts {
local function add(a: Int, b: Int) = a * b
local function divide(a: Int, b: Int) = a % b
["math"] {
add(3, 4) == 7
divide(8, 2) == 4
}
}
The error output now includes a power assertion diagram, which helps explain why the test failed.
module math
facts
✘ math
add(3, 4) == 7 (math.pkl:9)
│ │
12 false
divide(8, 2) == 4 (math.pkl:10)
│ │
0 false
0.0% tests pass [1/1 failed], 0.0% asserts pass [2/2 failed]
To learn more about this feature, consult SPICE-0026.
CLI Framework
Pkl 0.31 introduces a new framework for implementing CLI tools in Pkl (#1367, #1431, #1432, #1436, #1440, #1444).
The framework provides a way to build command line tools with user experience idioms that will be immediately familiar to users. CLI tools implemented in Pkl have largely the same capabilities as normal Pkl evaluation (i.e. writing to standard output and files), but this may be extended using external readers.
Commands are defined by extending the pkl:Command module:
extends "pkl:Command"
options: Options
class Options {
/// Mapping of <bird>=<bird age> pairs defining the list of birds.
@Argument
birds: Mapping<String, Number>
/// Aggregation function to apply to all bird ages.
aggregate: *"sum" | "mean" | "count"
}
class Bird {
/// Name of the bird.
name: String
/// Age of the bird in years.
age: Number
}
birds: Listing<Bird> = new {
for (_name, _age in options.birds) {
new { name = _name; age = _age }
}
}
result: Number =
if (options.aggregate == "sum")
birds.toList().fold(0.0, (result, bird) -> result + bird.age)
else if (options.aggregate == "mean")
birds.toList().fold(0.0, (result, bird) -> result + bird.age) / birds.length
else
birds.length
Commands are executed using the pkl run CLI subcommand:
$ pkl run bird-generator.pkl pigeon --aggregate=mean Pigeon=1 Hawk=8 Eagle=3
birds {
new {
name = "Pigeon"
age = 1
}
new {
name = "Hawk"
age = 8
}
new {
name = "Eagle"
age = 3
}
}
result = 4.0
Automatic CLI help is provided:
$ pkl run test.pkl -h
Usage: test.pkl [<options>] [<birds>]... <command> [<args>]...
Options:
--aggregate=<count, mean, sum> Aggregation function to apply to all bird ages.
-h, --help Show this message and exit
Arguments:
<birds> Mapping of <bird>=<bird age> pairs defining the list of birds.
To learn more about this feature, consult the documentation and SPICE-0025.
Noteworthy 🎶
Syntax Highlighting
The Pkl CLI displays Pkl code in several locations: stack frames within errors messages, power assertions, and in the REPL. This code is now syntax highlighted to improve readability (#1385, #1409):
CLI Support for Dependency Notation
The Pkl CLI now supports specifying modules using dependency notation (#1434, #1439). This is especially helpful for CLI commands defined in Packages:
$ pkl run @my-tool/cmd.pkl ...
This enhancement applies to the pkl eval, pkl run, pkl test, and pkl analyze imports commands.
It also applies to the pkldoc, pkl-codegen-java, and pkl-codegen-kotlin tools.
| Dependency notation only works for remote package dependencies. Local dependencies are not supported. |
Property Converter Annotations
Pkl provides the BaseValueRenderers.converters mechanism for transforming values during rendering.
Converters are flexible, but their design makes some transformations awkward.
In particular, modifying property names during rendering required a lot of extra code.
The new ConvertProperty annotation adds a way to express parameterized per-property name and value transformations (#1333).
To learn more about this feature, consult SPICE-0024.
Additional new Pkl APIs for per-format property renaming will be added for many built-in renderers:
import "pkl:json"
import "pkl:yaml"
@json.Property { name = "foo_bar" }
@yaml.Property { name = "foo-bar" }
fooBar: String = "hello world"
$ pkl eval fmt.pkl -f json
{
"foo_bar": "hello world"
}
$ pkl eval fmt.pkl -f yaml
foo-bar: hello world
Java API changes
New methods
New methods are introduced to the Java API.
-
org.pkl.core.Evaluator.evaluateCommand -
org.pkl.core.EvaluatorBuilder.setPowerAssertionsEnabled -
org.pkl.core.EvaluatorBuilder.getPowerAssertionsEnabled -
org.pkl.core.SecurityManager.resolveSecurePath -
org.pkl.config.java.ConfigEvaluator.evaluateOutputValue -
org.pkl.coznfig.java.ConfigEvaluator.evaluateExpression
Standard Library changes
New properties have been added to the standard library (#1396).
New module pkl:Command
The pkl:Command standard library module is added.
Breaking Changes 💔
Things to watch out for when upgrading.
Removal of @argfile support
Prior versions of Pkl had an undocumented feature allowing inclusion of CLI arguments from files using @path/to/file.
In order to support dependency notation on the CLI, @argfile support has been removed from Pkl.
Removal of Collection#transpose()
Prior versions of Pkl defined a transpose() method on the Collection class.
This method was never implemented and threw an error when called.
As it was never functional, it has been removed entirely without a deprecation warning (#1437).
Miscellaneous 🐸
-
Improve formatting of imports to keep surrounding comments (#1424).
-
Add support for evaluating module output and expressions to
ConfigEvaluator(#1297). -
The
pkl format --writecommand now exits successfully when formatting violations are found and updated (#1340). -
Add
pkl-bommodule to aid in aligning Pkl Java dependencies (#1390). -
Improve error message when writing
PklProject.deps.jsonfails (#1405). -
Add information about
Annotations to the language reference (#1427). -
Improved usability for the
org.pkl.formatter.FormatterJava API (#1428).
Bugs fixed 🐜
The following bugs have been fixed.
-
Function.toString()returns incorrect result (#1411). -
Failure when
--multiple-file-output-pathis a symlink (#1389). -
The
moduletype in a non-final module has default value of typeDynamic(#1392). -
The
moduletype is cached incorrectly in some cases (#1393). -
A possible race condition involving symlinks could bypass
--root-dirduring module and resource reading (#1426). -
pkl formatproduces internal stack traces when lexing fails (#1430). -
superaccess expressions are parsed incorrectly inside the spread operator (#1364). -
Modules and resources with
jar:file:URIs were not properly sandboxed by--root-dir(#1442).
Contributors 🙏
We would like to thank the contributors to this release (in alphabetical order):