Skip to main content

Pkl configuration

v1.32.0

While YAML is our official configuration format, we want to support dynamic formats, and as such, have added support for Pkl. What is Pkl? If you haven't heard of Pkl yet, Pkl is a programmable configuration format by Apple. We like Pkl, as it meets the following requirements:

  • Is easy to read and write.
  • Is dynamic and programmable (loops, variables, etc).
  • Has type-safety / built-in schema support.
  • Has Rust serde integration.

The primary requirement that we are hoping to achieve is supporting a configuration format that is programmable. We want something that has native support for variables, loops, conditions, and more, so that you could curate and compose your configuration very easily. Hacking this functionality into YAML is a terrible user experience in our opinion!

Installing Pkl

Pkl utilizes a client-server architecture, which means that the pkl binary must exist in the environment for parsing and evaluating .pkl files. Jump over to the official documentation for instructions on how to install Pkl.

If you are using proto, you can install Pkl with the following commands.

proto plugin add pkl https://raw.githubusercontent.com/milesj/proto-plugins/refs/heads/master/pkl.toml
proto install pkl --pin

Using Pkl

To start using Pkl in moon, simply:

info

We highly suggest reading the Pkl language reference, the standard library, or looking at our example configurations when using Pkl.

Caveats and restrictions

Since this is an entirely new configuration format that is quite dynamic compared to YAML, there are some key differences to be aware of!

  • Only files are supported. Cannot use or extend from URLs.

  • Each .pkl file is evaluated in isolation (loops are processed, variables assigned, etc). This means that task inheritance and file merging cannot extend or infer this native functionality.

  • default is a special feature in Pkl and cannot be used as a setting name. This only applies to template.pkl, but can be worked around by using defaultValue instead.

template.pkl
variables {
["age"] {
type = "number"
prompt = "Age?"
defaultValue = 0
}
  • local is also a reserved word in Pkl. It can be worked around by escaping it with backticks, or you can simply use the preset setting instead.
tasks {
["example"] {
`local` = true
# Or
preset = "server"
}
}

Example configs

.moon/workspace.pkl

projects {
globs = List("apps/*", "packages/*")
sources {
["root"] = "."
}
}

vcs {
defaultBranch = "master"
}

.moon/toolchain.pkl

node {
version = "20.15.0"
packageManager = "yarn"
yarn {
version = "4.3.1"
}
addEnginesConstraint = false
inferTasksFromScripts = false
}

moon.pkl

type = "application"
language = "typescript"
dependsOn = List("client", "ui")

tasks {
["build"] {
command = "docusaurus build"
deps = List("^:build")
outputs = List("build")
options {
interactive = true
retryCount = 3
}
}
["typecheck"] {
command = "tsc --build"
inputs = new Listing {
"@globs(sources)"
"@globs(tests)"
"tsconfig.json"
"/tsconfig.options.json"
}
}
}

Example functionality

Loops and conditionals

tasks {
for (_os in List("linux", "macos", "windows")) {
["build-\(_os)"] {
command = "cargo"
args = List(
"--target",
if (_os == "linux") "x86_64-unknown-linux-gnu"
else if (_os == "macos") "x86_64-apple-darwin"
else "i686-pc-windows-msvc",
"--verbose"
)
options {
os = _os
}
}
}
}

Local variables

local _sharedInputs = List("src/**/*")

tasks {
["test"] {
// ...
inputs = List("tests/**/*") + _sharedInputs
}
["lint"] {
// ...
inputs = List("**/*.graphql") + _sharedInputs
}
}