ROOT:partial$component-attributes.adoc
Pkl 0.30 was released on November 3rd, 2025.
The latest bugfix release is 0.30.2. (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 {uri-pkl-roadmap}[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.
Pkl now has a formatter (#1107, #1208, #1211, #1215, #1217, #1235, #1246, #1247, #1252, #1256, #1259, #1260, #1263, #1265, #1266, #1267, #1268, #1270, #1271, #1272, #1273, #1274, #1280, #1283, #1289, #1290)!
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.
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 --write (-w) flag.
This also implies the --diff-name-only flag.
pkl format -w .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. |
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.
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 {uri-stdlib-pklbinaryModule}[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.
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.
Users of Pkl’s language binding libraries can decode pkl-binary data into instances of code-generated types.
- Java
-
var encodedData = fetchEncodedData(); // some byte[] or InputStream var config = Config.fromPklBinary(encodedData); var appConfig = config.as(AppConfig.class);
- Kotlin
-
val encodedData = fetchEncodedData() // some ByteArray or InputStream val config = Config.fromPklBinary(encodedData, ValueMapper.preconfigured().forKotlin()) val appConfig = config.to<AppConfig>()
- Go
-
encodedData := fetchEncodedData() // some []byte var appConfig AppConfig if err := pkl.Unmarshal(encodedData, &appConfig); err != nil { // handle error }
- Swift
-
let encodedData = fetchEncodedData() // some [UInt8] let appConfig = try PklDecoder.decode(AppConfig.self, from: encodedData)
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
}-
Function parameter lists in method definitions.
-
Argument lists in method calls.
-
Type parameter lists in generic type definitions.
-
Type argument lists in type annotations and casts.
-
Type constraint lists.
-
Function type parameter lists in function type annotations.
-
Object body parameter lists.
To learn more about this change, consult SPICE-0019.
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.pklThanks to @ssalevan for their contribution to this feature!
Previously, attempting to render a Bytes value using {uri-stdlib-YamlRenderer}[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)-
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)-
Result:
Bytes(1, 2, 3)
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.
New modules, properties, methods, classes and typealiases have been added to the standard library (#1106).
Additions:
-
New class: {uri-stdlib-BaseValueRenderer}[
BaseValueRenderer] -
{uri-stdlib-ValueRenderer}[
ValueRenderer] (now a subclass of {uri-stdlib-BaseValueRenderer}[BaseValueRenderer]) -
{uri-stdlib-BytesRenderer}[
BytesRenderer] -
{uri-stdlib-baseModule}/RenderDirective#bytes[
RenderDirective.bytes]
-
{uri-stdlib-reflectModule}/Class#allMethods[
Class.allMethods] -
{uri-stdlib-reflectModule}/Class#allProperties[
Class.allProperties] -
{uri-stdlib-reflectModule}/Property#allAnnotations[
Propterty.allAnnotations] -
{uri-stdlib-reflectModule}/Propterty#allModifiers[
Propterty.allModifiers]
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.
-
Things to watch out for when upgrading.
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.
For users of Pkl’s Kotlin libraries, the minimum Kotlin version has been bumped to 2.1 (#1232).
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.
Due to breaking changes made in pkldoc’s data model, existing pkldoc websites must be migrated.
See Migration for more details.
-
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).
We would like to thank the contributors to this release (in alphabetical order):