Pkl 0.30 Release Notes
Pkl 0.30 was released on November 3rd, 2025.
The latest bugfix release is 0.30.0. (All Versions)
This release introduces a code formatter, and an in-language API for producing pkl-binary output.
The next release (0.31) is scheduled for February 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 💖
Formatter
Pkl now has a formatter (#1107, #1208, #1211, #1215, #1217, #1235, #1246, #1247, #1252, #1256, #1259, #1260, #1263, #1265, #1266, #1267, #1270, #1271, #1272, #1273, #1274, #1280)!
Pkl’s formatter is canonical, which means that it has a single set of formatting rules, with (almost) no configuration options. The goal is to eliminate the possibility of formatting debates, which can lead to churn and bike-shedding.
The formatter is available both as a CLI subcommand and as a Java API.
To learn more about this feature, consult SPICE-0014.
Using the CLI
The Pkl formatter is available under the pkl format subcommand. By default, the command will concatenate and write all formatted content to standard out.
To simply check for formatting violations, getting formatted output on stdout is likely too verbose.
The --silent flag can be used to omit any output, and the exit code can be used to determine
if there are formatting violations.
pkl format --silent . || echo "Formatting violations were found!"
Alternatively, the --names flag will print out the names of any files that have formatting violations.
pkl format --diff-name-only .
To apply formatting, use the --overwrite (-w) flag.
This also implies the --diff-name-only flag.
pkl format -w .
Exit codes
The formatter will exit with the following codes:
| Code | Description |
|---|---|
0 |
No formatting violations were found. |
1 |
Non-formatting errors occurred. |
11 |
Formatting violations were found. |
Grammar version
The formatter can be configured with a grammar version, which maps to a Pkl version range.
| Grammar version | Pkl versions |
|---|---|
1 |
0.25 - 0.29 |
2 |
0.30+ |
Grammar version 2 uses the newly introduced trailing commas feature, and therefore is not compatible with existing versions of Pkl.
To ensure compatibility, use the --grammar-version 1 CLI flag.
pkl-binary in-language Renderer
A new in-language API has been added to render values into pkl-binary encoding (#1203, #1250, #1275).
It’s sometimes useful to separate Pkl evaluation from data consumption when used as application or service configuration.
This is possible with the pkl-binary format, which is a lossless encoding of Pkl data.
In this approach, Pkl is first rendered into pkl-binary during build time, and then deserialized into classes and structs at startup time.
This means that the Pkl evaluator does not need to be shipped with the application, which improves code portability and eliminates runtime overhead.
However, currently, the API for getting this binary format is somewhat cumbersome.
Only the host languages have access to this API (for example, evaluateExpressionRaw in pkl-swift).
This means that one-off logic must be written to render this format in the host language.
In 0.30, this renderer is added as an in-language API, through the pkl:pklbinary standard library module.
Additionally, it is available through CLI flag --format pkl-binary.
For example:
import "pkl:pklbinary"
output {
renderer = new pklbinary.Renderer {}
}
To learn more about this feature, consult SPICE-0021.
New renderer type: BytesRenderer
To enable the pkl-binary renderer feature, Pkl now supports renderers that produce Bytes output (#1203).
The existing ValueRenderer class now extends new class BaseValueRenderer, and a new BytesRenderer class is introduced.
Setting a module’s output renderer to a BytesRenderer will control its resulting output.bytes.
This affects usage scenarios where a module’s output.bytes is evaluated, for example, when using pkl eval.
Using pkl-binary data
Users of Pkl’s language binding libraries can decode pkl-binary data into instances of code-generated types.
-
Java
-
Kotlin
-
Go
-
Swift
var encodedData = fetchEncodedData(); // some byte[] or InputStream
var config = Config.fromPklBinary(encodedData);
var appConfig = config.as(AppConfig.class);
val encodedData = fetchEncodedData() // some ByteArray or InputStream
val config = Config.fromPklBinary(encodedData, ValueMapper.preconfigured().forKotlin())
val appConfig = config.to<AppConfig>()
encodedData := fetchEncodedData() // some []byte
var appConfig AppConfig
if err := pkl.Unmarshal(encodedData, &appConfig); err != nil {
// handle error
}
let encodedData = fetchEncodedData() // some [UInt8]
let appConfig = try PklDecoder.decode(AppConfig.self, from: encodedData)
Noteworthy 🎶
Trailing Commas
Pkl’s grammar has been updated to allow including a comma following the final item of comma-separated syntax elements (#1137).
These syntax elements are affected:
function add(
bar,
baz, (1)
) = bar + baz
foo = add(
1,
2, (2)
)
typealias MyMapping<
Key,
Value, (3)
> = Mapping<Key, Value>
bar: Mapping<
String,
Int, (4)
>
baz: Mapping(
!containsKey("forbidden key"),
!containsKey("forbidden value"), (5)
)
qux: (
String,
Int, (6)
) -> Dynamic =
(paramA, paramB) -> new Dynamic {
quux = "\(paramA): \(paramB)"
}
corge = (qux) {
paramA,
paramB, (7)
->
grault = paramA.length * paramB
}
| 1 | Function parameter lists in method definitions. |
| 2 | Argument lists in method calls. |
| 3 | Type parameter lists in generic type definitions. |
| 4 | Type argument lists in type annotations and casts. |
| 5 | Type constraint lists. |
| 6 | Function type parameter lists in function type annotations. |
| 7 | Object body parameter lists. |
To learn more about this change, consult SPICE-0019.
Pretty-printed traces
Currently, the trace() operator will render values as a single line, and trims the output after 100 characters.
This can obscure information when debugging.
In 0.30, a new evaluator option is added to control how traces are emitted. Two trace modes are introduced:
-
compact- the current output mode (default). -
pretty- multiline, with no limit on output size.
For example, users of the CLI can specify --trace-mode as a flag.
pkl eval --trace-mode pretty myModule.pkl
Thanks to @ssalevan for their contribution to this feature!
Better support for Bytes when rendering YAML
Previously, attempting to render a Bytes value using YamlRenderer required the use of a converter.
Now, Pkl can natively render YAML containing binary scalars (#1276).
foo {
bar = Bytes(1, 2, 3)
}
rendered = new YamlRenderer {}.renderValue(foo) (1)
| 1 | Result: bar: !!binary AQID |
Similarly, yaml.Parser now parses binary YAML values as Pkl Bytes values (#1277).
This is a breaking change; previously these values were parsed as String value containing the base64-encoded binary data.
import "pkl:yaml"
yamlData =
"""
bytes: !!binary AQID
"""
parsed = new yaml.Parser {}.parse(yamlData).bytes (1)
| 1 | Result: Bytes(1, 2, 3) |
pkldoc performance improvements
Currently, the pkldoc tool needs to consume much of an existing documentation website whenever generating new documentation.
This adds significant I/O overhead as a pkldoc documentation website grows.
The generator has been overhauled to minimize the amount of data needed to be read from the current site.
To read more about this change, consult SPICE-0018.
Migration
The new pkldoc website introduces breaking changes to the data model.
Because of this, existing sites must be migrated before using the 0.30 version of pkldoc.
To migrate, run the one-time command pkldoc --migrate.
Java API changes
New methods
New methods are introduced to the Java API.
-
org.pkl.core.Evaluator.evaluateExpressionPklBinary -
org.pkl.core.EvaluatorBuilder.setTraceMode -
org.pkl.core.EvaluatorBuilder.getTraceMode -
org.pkl.config.java.Config.fromPklBinary
Standard Library changes
New modules, properties, methods, classes and typealiases have been added to the standard library (#1106).
Changes to pkl:base
Additions:
-
New class:
BaseValueRenderer -
ValueRenderer(now a subclass ofBaseValueRenderer)
New module: pkl:pklbinary
The pkl:pklbinary standard library module is added.
pkl repl improvements
The REPL now handles interrupts (Ctrl-C) in a more user-friendly way (#1188).
Instead of exiting immediately, it behaves like other REPLs:
-
If the line is non-empty or in a continuation, the buffer is cleared.
-
If the line is empty, print a message with instructions on exiting the REPL.
-
If another interrupt is sent immediately after, exit.
-
Breaking Changes 💔
Things to watch out for when upgrading.
Binary data handling yaml.Parser
YAML binary scalars are now parsed as Bytes values.
Prior versions of Pkl parsed binary scalars as String values containing the base64-encoded binary data.
Minimum Kotlin version bump
For users of Pkl’s Kotlin libraries, the minimum Kotlin version has been bumped to 2.1 (#1232).
New base module names: BaseValueRenderer, BytesRenderer
Two new names are introduced to the base module: BaseValueRenderer, and BytesRenderer.
That means that any code that currently references these names on implicit this will break (#1203).
To learn more about how this breaks code and how to fix it, see New base module names in 0.29’s release notes.
pkldoc sites need to be migrated
Due to breaking changes made in pkldoc’s data model, existing pkldoc websites must be migrated.
See Migration for more details.
Miscellaneous 🐸
-
Enforce Pkl formatting of stdlib (#1236, #1253, #1258, #1278, #1279).
-
Add internal IntelliJ plugin that’s meant to assist with development of the Pkl codebase itself (#1248).
-
Update CircleCI macOS instance type and Xcode version (#1243, #1244).
-
Disable multi-jdk testing when running on Windows ARM (#1223).
-
Refine documentation for class
Any(#1194).
Contributors 🙏
We would like to thank the contributors to this release (in alphabetical order):