How to Use Pkl: The Beginner's Guide (First Install, the Type Lesson, First Eval, and the YAML-Replacement Arc)

CodexFuse Team · 2026-11-02T01:30:26.355Z · 13 min read

Learn how to use Pkl, Apple’s typed configuration language: install it, write your first typed config, catch errors at evaluation time, and generate JSON, YAML, and Kubernetes manifests.

What Is Pkl, Exactly?

Pkl (pronounced "pickle") is Apple's open-source configuration language, first released in early 2024 under the Apache 2.0 license. If you are learning how to use Pkl, start with the mental model its makers use: Pkl is TypeScript for configuration files — a real language with types and validation that you evaluate once to produce the plain JSON, YAML, XML, plist, or properties files your apps already consume.

One clarification up front, because search results mix them together: Pkl is not Python's pickle module. Python pickle serializes Python objects into .pkl files with pickle.dump; Apple's Pkl is a configuration language you write by hand. Searching "pkl apple" lands you on the official docs; searching bare "pkl" more often lands you on pickle tutorials instead.

This pkl tutorial follows the arc the field is missing: install the CLI, learn the type system, run your first pkl eval, then generate real Kubernetes manifests. Unlike JSON or YAML, the Pkl configuration language is evaluated, not just parsed — and that single difference is what makes invalid configs impossible to deploy.

The payoff is concrete if you ship containers: one typed Pkl file can generate the YAML that the Docker beginner guide workflows consume — write the config once, emit correct manifests for every environment, and let the evaluator catch your typos before they reach production.

How Do You Install Pkl?

You install Pkl the same way you install any developer CLI — and the current release as of this writing is 0.32.2, published 2026-10-21. Pick the route that matches your machine:

PlatformCommandNotes
macOS / Linux
brew install pkl
Official Homebrew formula; upgrade later with brew upgrade pkl
macOS / Linux / Windows
mise use -g [email protected]
Version-manager route; drop -g for a project-local pin
Windows
winget install Apple.Pkl
Official winget package
Linux (manual)
curl -L -o pkl 'https://github.com/apple/pkl/releases/download/0.32.2/pkl-linux-amd64'
chmod +x pkl
./pkl --version
Native binary, no package manager; aarch64 and Alpine builds also ship
Anywhere with Java 17+
curl -L -o jpkl 'https://repo1.maven.org/maven2/org/pkl-lang/pkl-cli-java/0.32.2/pkl-cli-java-0.32.2.jar'
chmod +x jpkl
./jpkl --version
The Java executable, named jpkl

Skip the install.sh one-liner you may see in older tutorials. The address pkl-lang.org/install.sh returns a 404 today — it was never Apple's official installer, and piping it is exactly the kind of unverified setup step Pkl's whole philosophy argues against. Verify the install with pkl --version, which should print 0.32.2.

If you have no Linux machine to practice on, the VPS hosting guide walks through renting one for a few dollars a month — the manual binary route above takes about a minute once you can SSH in.

What Does pkl eval Actually Do?

pkl eval is the core verb of the whole language: it evaluates a Pkl module and renders the result. Here is the first surprise for beginners — running pkl eval config.pkl with no flags does not print JSON.

The default renderer produces Pcf, Pkl's own configuration format. Think of bare pkl eval as "show me what Pkl thinks this file means," and the -f flag as "give me the artifact my app actually consumes."

// config.pkl
module AppConfig

host: String = "localhost"
port: UInt16 = 3000
$ pkl eval config.pkl -f json
{
  "host": "localhost",
  "port": 3000
}

$ pkl eval config.pkl -f yaml
host: localhost
port: 3000

The -f (--format) flag accepts json, jsonnet, pcf, plist, properties, xml, and yaml. Two more flags cover the everyday workflow: -o writes the output to a file instead of stdout, with placeholders like %{moduleName} and %{outputFormat} when you evaluate several modules at once, and -x evaluates a single expression inside a module, as in pkl eval -x metadata.species pigeon.pkl. Larger setups declare package dependencies in a PklProject file and point the evaluator at it with --project-dir.

How Do Pkl's Types Catch Bad Configs Before Deploy?

Types are the entire reason Pkl exists: a config that cannot be wrong at runtime because the evaluator refused to render it. Pkl's basic types will feel familiar — String, Boolean, Float, and Int, where Int is a 64-bit signed integer — plus range types like UInt8 and UInt16 that bake "this number fits in a port" directly into the type.

hostname: String = "api.example.com"
port: UInt16 = 8080
maxRetries: Int = 3
typealias Environment = "dev"|"qa"|"prod"
env: Environment = "prod"

Constraints are where the real protection lives. A constraint is a predicate attached to a type, and the evaluator enforces it every time the file is rendered:

name: String(!isEmpty) = "billing-api"
age: Int(isBetween(0, 130)) = 42
zipCode: String(matches(Regex("[0-9]{5}"))) = "10001"
serverPort: Int(isBetween(0, 1023)) = 443

Now watch what happens when a value breaks the contract. Amending a module with age = -5 produces the official error, verbatim:

–– Pkl Error ––
Type constraint `isBetween(0, 130)` violated.
Value: -5

The same failure happens inline. With port: Int(this > 1000) = 80, the evaluator reports that the type constraint `this > 1000` is violated by the value 80.

The error arrives at evaluation time, on your machine, before anything is deployed — which is the whole pitch. Teach yourself one failing example like this and you will never again trust a hand-written YAML port field.

How Do amends and extends Work?

Real configs are never one flat file — they are a base plus environment overrides. Pkl models this with two keywords.

A class is a reusable template, amends specializes a whole module, and extends specializes a class. Write the schema once as a class, then amend it per environment:

// Application.pkl
module Application

class Database {
  username: String
  password: String
  host: String
  port: UInt16
  dbName: String
}

database: Database
// Production.pkl
amends "Application.pkl"

database {
  username = "svc-billing"
  host = "db.internal"
  port = 5432
  dbName = "billing"
}

Every module header is built from the same three optional clauses: the module clause (module Application), one amends or extends clause, and any import clauses. Use import when you need another module's definitions without specializing it, and extends inside class hierarchies — class Penguin extends Bird is the reference example — when one template refines another.

Packages work the same way at a larger scale. A PklProject file declares dependencies on versioned packages, and modules pull them in by URI — for example, import "package://pkg.pkl-lang.org/pkl-pantry/[email protected]#/toml.pkl", or the shorter import "@toml/toml.pkl" once the PklProject maps the name.

Commit the PklProject file to git the way you would a lockfile: it is what makes your teammate's evaluation produce byte-identical output. If you version your infrastructure configs in a repo, the Git beginner guide habits — commit early, review the diff — apply to Pkl files exactly like code. Once the pkl amends extends pattern clicks — base module, class templates, per-environment amendments — multi-environment configs become boring in the best way.

Pkl vs YAML: When Is the Switch Worth It?

Pkl does not ask you to abandon YAML — it generates YAML. The honest comparison is about where each format earns its keep:

ConcernPlain YAMLPkl
ValidationNone — a typo in a port number deploys fineTypes and constraints fail at evaluation time
TypesEverything is a string until your app parses itString, Int, UInt16, Boolean, and custom aliases
CommentsSupportedSupported, plus doc comments for tooling
Reuse across environmentsCopy-paste, anchors, or external templatingamends and extends, checked by the evaluator
Multiple output formatsOne file, one formatOne source emits JSON, YAML, XML, plist, properties
Learning curveZeroAn afternoon for the type system basics

Here is the graduation line, stated once: for a 10-line config, plain YAML wins and Pkl is overkill. Pkl pays for itself when configs grow teeth — multiple environments, shared schemas, generated Kubernetes manifests, or any file where a silent typo has ever caused an incident. Start with YAML, switch the day you catch yourself writing a YAML linter in a shell script.

Pkl vs Cue: Should You Compare Them?

Pkl is not the only language in the configuration-language lane. Cue is the closest-known alternative — a constraint-based config language with its own type system — and Dhall, Jsonnet, and Starlark occupy neighboring corners of the same space. They all answer the same complaint: untyped YAML fails silently.

The practical difference for a beginner is ecosystem, not philosophy. Pkl ships the evaluator, the VS Code and IntelliJ plugins, the language server, packages like pkl-k8s, and code generation for Java, Kotlin, Swift, and Go from one vendor.

If you are already in Apple's orbit of tooling or you want the gentlest on-ramp, Pkl is the easier first step. This guide covers Pkl only; Cue, Dhall, Jsonnet, and Starlark stay comparison members of the same lane and will never get rival beginner pages here.

How Do You Generate Kubernetes Manifests with Pkl?

You generate Kubernetes manifests with Pkl through the official pkl-k8s package: declare the dependency, import the typed templates, and evaluate to YAML. The pkl kubernetes workflow centers on version 1.4.1 of that package, published 2026-03-06, whose templates are generated from Kubernetes' own OpenAPI spec — so the fields your Deployment accepts are typed and validated before anything touches a cluster.

Declare the dependency in your PklProject file, then import the pieces you need:

// PklProject
amends "pkl:Project"

dependencies {
  ["k8s"] {
    uri = "package://pkg.pkl-lang.org/pkl-k8s/[email protected]"
  }
}
// deployment.pkl
import "@k8s/K8sResource.pkl"
import "@k8s/api/apps/v1/Deployment.pkl"

deployment: Deployment = new {
  metadata {
    name = "billing-api"
  }
  spec {
    replicas = 3
  }
}
$ pkl eval -f yaml deployment.pkl > deployment.yaml

A typo in a field name or an out-of-range replica count now fails in your terminal instead of being rejected by the API server — or worse, applied silently. If Kubernetes itself is new to you, read the Kubernetes beginner guide first so the manifest fields mean something; Pkl makes the YAML correct, but you still decide what correct means.

How Do You Use Pkl in VS Code?

A configuration language lives or dies by its editor support, and Pkl ships an official VS Code extension (maintained in the apple/pkl-vscode repository) with syntax highlighting, autocompletion, and inline error reporting. The pkl vscode experience is the fastest way to feel the type system: the red squiggle under port: UInt16 = 99999 appears as you type, not at deploy time. JetBrains IDEs get the same treatment through apple/pkl-intellij, Neovim through apple/pkl-neovim, and every other editor can integrate through the Pkl Language Server, which implements the standard Language Server Protocol.

The language server is the quiet hero of the beginner experience: hover documentation on standard-library members, go-to-definition across amends chains, and rename refactoring all work because the editor understands Pkl as a language, not as text. Install the extension for your editor before you write your first real config — it turns the type lesson from theory into muscle memory.

7 Mistakes Every Pkl Beginner Makes

Every mistake below is one I have watched a real beginner make — most of them are the exact traps this guide was written to defuse:

MistakeWhat happensThe fix
Running bare pkl eval and expecting JSONYou get Pcf, Pkl's own format, and wonder what went wrongAlways pass -f json or -f yaml when you want a consumable artifact
Piping the install.sh one-liner404 — it was never Apple's installerUse brew, mise, winget, or the official release binary
Writing untyped fields everywhereYou rebuilt YAML with extra steps and kept all its failure modesType every field; add isBetween or matches where values have legal ranges
Editing the generated YAML instead of the .pklYour fix vanishes the next time someone re-evaluatesThe .pkl file is the source of truth; generated files are build output
Floating the pkl-k8s version in PklProjectManifests drift between machines as the package updatesPin the version, e.g. [email protected], and bump it deliberately
Putting secrets directly in a .pkl fileeval renders them in plaintext into every output formatSame secret hygiene as any config: references or a secrets manager, never literals
Reaching for Pkl on a 10-line configAn afternoon of learning for zero payoffPlain YAML wins small; adopt Pkl when configs grow environments or schemas

Sources

Every version number, command, and error message in this guide was verified against pages fetched on 2026-11-02:

  1. Apple Pkl releases — current version 0.32.2 published 2026-10-21, release assets for macOS, Linux, Alpine, Windows, jpkl, and codegen CLIs: https://github.com/apple/pkl and https://api.github.com/repos/apple/pkl/releases/latest
  2. Pkl CLI reference — official install routes (brew, mise, winget, Linux binary, jpkl) and the pkl eval synopsis: https://pkl-lang.org/main/0.29.1/pkl-cli/index.html and https://pkl-lang.org/main/latest/pkl-cli/index.html
  3. Introducing Pkl — Apple's launch announcement: the "programming language for producing configuration," pronounced "pickle," with the Application/Database class example, the typealias Environment example, the constraint examples (String(!isEmpty), Int(isBetween(0, 130)), matches(Regex)), and the verbatim isBetween(0, 130) failing evaluation: https://pkl-lang.org/blog/introducing-pkl.html
  4. Pkl language reference — numeric types (Int is 64-bit, Float, Number supertype), range restrictions (clientPort: UInt16, serverPort: Int(isBetween(0, 1023))), extends example, and the "pkl eval config.pkl renders Pcf" default: https://pkl-lang.org/main/current/language-reference/index.html and https://pkl-lang.org/main/0.25.3/language-reference/index.html
  5. Pkl style guide — module header clauses (module, amends/extends, imports): https://pkl-lang.org/main/0.27.2/style-guide/index.html
  6. pkl-lang.org homepage — code generation for Java, Kotlin, Swift, and Go; editor plugins for IntelliJ, VS Code, and Neovim; the Pkl Language Server; the verbatim port: Int(this > 1000) = 80 failing example: https://pkl-lang.org/
  7. apple/pkl-k8s — "Templates for using Pkl with Kubernetes," packages at package://pkg.pkl-lang.org/pkl-k8s/k8s@<VERSION>, current [email protected] published 2026-03-06: https://github.com/apple/pkl-k8s and https://api.github.com/repos/apple/pkl-k8s/releases/latest
  8. Pkl with Kubernetes and Prometheus — current-version PklProject + @k8s import usage pattern: https://lucaberton.com/blog/pkl-configuration-language-kubernetes-prometheus/
  9. pkl-go codegen docs — pkl-gen-go installed via go install github.com/apple/pkl-go/cmd/pkl-gen-go@latest: https://pkl-lang.org/go/0.7.0/codegen.html
  10. Apple Pkl concepts intro — early-2024 launch context and the pkl eval -f json/plist render examples: https://configu.com/blog/apple-pkl-code-example-concepts-how-to-get-started/

Frequently Asked Questions

Is Pkl the same as Python's pickle?

No. Python's pickle module serializes Python objects into .pkl files with pickle.dump and pickle.load; Apple's Pkl (pronounced "pickle") is a configuration language you write by hand, first released in early 2024 under the Apache 2.0 license. They share nothing but a similar-sounding name, so disambiguate before you search.

How do I start learning how to use Pkl?

Install the CLI (brew install pkl is the fastest route), write a three-line config with one typed field, and run pkl eval -f yaml on it. Then add one constraint — port: UInt16 or Int(isBetween(0, 1023)) — and deliberately break it to see the evaluator refuse. That five-minute loop teaches more than any reference page.

What does pkl eval output by default?

Pcf, Pkl's own configuration format — bare pkl eval config.pkl is shorthand for pkl eval --format pcf config.pkl. Always pass -f json, -f yaml, or another format flag when you want an artifact your application can consume.

Can Pkl replace YAML in my Kubernetes workflow?

Yes, through the official pkl-k8s package (currently 1.4.1): declare it in a PklProject file, import the typed templates like @k8s/api/apps/v1/Deployment.pkl, and evaluate with pkl eval -f yaml to generate manifests. Field names and value ranges are validated before anything reaches your cluster.

Which languages can consume Pkl-generated configs?

Any language, because Pkl emits standard formats — JSON, YAML, XML, plist, and properties are all plain data your existing parsers already read. For tighter integration, Apple ships code generation for Java, Kotlin, Swift, and Go, plus evaluator bindings (notably pkl-go and pkl-swift), so applications can load .pkl files directly.

Featured on Twelve Tools Launch Llama Tools Launch your MCP