YAML is the configuration format that everyone loves to complain about and nobody replaces. It’s ambiguous (Norway problem, anyone?), error-prone, has no native type validation, and provides no way to express constraints on values. Every project that uses YAML heavily ends up building its own layer of validation logic around it because YAML itself offers nothing in that direction.

Pkl (pronounced “Pickle”) is Apple’s attempt at a better answer. Open-sourced in February 2024 under the Apache 2.0 licence, it’s a purpose-built configuration language with a type system, constraint validation, template inheritance, and the ability to output multiple formats including JSON, YAML, TOML, and XML. By 2026 it’s moved from curiosity to genuine adoption in infrastructure-heavy teams, particularly around Kubernetes configuration management.

What Pkl Actually Is

Pkl is a standalone language that generates configuration rather than being used as runtime configuration directly. You write .pkl files, run the Pkl CLI, and get JSON, YAML, or another format as output. The generated output is what your systems actually consume — Pkl is a compile step in your configuration pipeline.

This is an important distinction. You’re not replacing YAML in Kubernetes with Pkl — Kubernetes still consumes YAML. What you’re doing is generating that YAML from Pkl templates that have type validation, making the configuration authoring and maintenance experience significantly better.

Basic Syntax

Pkl looks a bit like a mix of Kotlin and Python. Here’s a simple example:

module AppConfig

name: String = "my-app"
port: Int(isBetween(1024, 65535)) = 8080
replicas: Int(isPositive) = 3
environment: "production" | "staging" | "development" = "production"

logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR" = "INFO"

The constraint syntax (isBetween(1024, 65535), isPositive) is built into the type system. If someone sets port = 80 (below 1024), Pkl throws an error at evaluation time. If they set environment = "prod" (not one of the valid union type values), Pkl errors. This type of validation is what’s entirely absent from plain YAML.

Outputting this as JSON:

pkl eval -f json config.pkl

Classes and Templates

Where Pkl gets genuinely useful is in templates — reusable config structures that can be extended and overridden:

abstract module ServiceTemplate

host: String
port: Int(isBetween(1, 65535))
replicas: Int(isPositive) = 1
healthCheckPath: String = "/health"

resources {
  cpu: String = "250m"
  memory: String = "256Mi"
}

An environment-specific file amends this:

amends "ServiceTemplate.pkl"

host = "api.production.example.com"
port = 443
replicas = 5

resources {
  cpu = "1000m"
  memory = "1Gi"
}

The production config inherits all defaults from the template and overrides what it needs. The template enforces the types. Any field left undefined takes its default. The result is a configuration hierarchy that’s self-documenting and validated without any extra tooling.

Kubernetes Integration

The io.k8s.convert module is where Pkl has made the most practical headway. It provides Pkl classes that map to Kubernetes API types, letting you write Kubernetes manifests in Pkl and output them as YAML:

import "package://pkg.pkl-lang.org/pkl-k8s/k8s@1.1.0#/apps/v1/Deployment.pkl"

local deployment = new Deployment {
  metadata {
    name = "my-api"
    namespace = "production"
    labels { app = "my-api" }
  }
  spec {
    replicas = 3
    selector {
      matchLabels { app = "my-api" }
    }
    template {
      metadata {
        labels { app = "my-api" }
      }
      spec {
        containers {
          new {
            name = "api"
            image = "my-api:1.2.3"
            ports { new { containerPort = 8080 } }
          }
        }
      }
    }
  }
}

output {
  value = deployment
}

Running pkl eval -f yaml deployment.pkl produces valid Kubernetes YAML. The advantage over writing YAML directly: if you get a field name wrong or put a value of the wrong type, Pkl catches it before you try to kubectl apply. The Kubernetes API spec is encoded in the Pkl types.

The CLI and IDE Support

The Pkl CLI is the main tool:

# Evaluate to YAML
pkl eval -f yaml config.pkl

# Evaluate to JSON
pkl eval -f json config.pkl

# Type-check without evaluating
pkl check config.pkl

# Generate Pkl classes from JSON schema
pkl gen-kotlin --schema openapi.json  # generates bindings for multiple languages

IDE support exists via extensions for IntelliJ (official, from Apple) and VS Code (community extension with reasonable functionality). The IntelliJ plugin is the more complete of the two — code completion, go-to-definition, inline error highlighting, and module resolution all work well. The VS Code extension covers the basics but is less polished.

How It Compares

The obvious comparison is with other structured configuration alternatives:

CUE: Similar goals to Pkl — typed configuration, constraint validation, multiple output formats. CUE has been around longer and has a larger library ecosystem. Its syntax is more terse and takes longer to learn; Pkl’s is more approachable for teams that haven’t previously used either. CUE’s unification model is theoretically more powerful; Pkl’s template inheritance is more intuitive in practice.

Jsonnet: Template language with imports and functions, outputs JSON. No type system, no constraint validation. Better than YAML for templating, worse than Pkl for validation. Still widely used in legacy infrastructure tooling.

Dhall: Typed configuration language with strong guarantees (total functions, no infinite loops). Powerful but adoption has been limited by syntax complexity and a smaller ecosystem.

Helm (for Kubernetes specifically): Template language layered on YAML, not a new configuration language. Helm charts remain more widely supported than Pkl for Kubernetes package management, but writing Helm templates is genuinely painful in ways that Pkl avoids.

When Pkl Makes Sense

Pkl is most valuable when your configuration has enough complexity that errors are costing you time — wrong types reaching production, misconfigured replicas, ports out of valid range, environment names misspelled. If your configuration is simple and changes rarely, the Pkl compile step adds overhead without much benefit.

It’s specifically worth considering for:

  • Multi-environment infrastructure configuration (production/staging/dev variations of the same base config)
  • Kubernetes manifests that are maintained by developers rather than dedicated platform engineers
  • Internal configuration files that need schema enforcement — database configs, feature flag definitions, service registry entries
  • Anywhere you’ve already built ad-hoc validation scripts around YAML

The adoption curve is real — teams need to learn new syntax, integrate the CLI into their CI pipeline, and update any tooling that generates or consumes configuration directly. For greenfield infrastructure projects, the investment pays off quickly. For mature codebases with extensive existing YAML, the migration cost needs to be weighed against the validation and maintainability benefits.

Pkl isn’t going to replace YAML universally. But for the specific problem of validated, typed, templated configuration generation, it’s the most developer-friendly tool in this space in 2026.