Ir al contenido

Pkl Cheat Sheet

Overview

Pkl (pronounced “pickle”) is a configuration-as-code language created by Apple and released as open source in 2024. It provides a programmable, scalable, and safe approach to configuration that sits between static formats like JSON/YAML and full programming languages. Pkl combines the simplicity of declarative configuration with the power of a real programming language, including types, validation, functions, conditionals, and code generation for multiple target languages.

Pkl addresses the growing complexity of configuration management by providing schemas for validation, IDE support with autocompletion, and the ability to generate configuration in JSON, YAML, XML, Property Lists, and more. It supports modularity through imports and inheritance, enabling teams to create reusable configuration templates. Pkl code can generate type-safe bindings for Java, Kotlin, Swift, and Go, ensuring configuration values match expected types at compile time.

Installation

Package Managers

# macOS
brew install pkl

# Linux (binary)
curl -L -o pkl https://github.com/apple/pkl/releases/latest/download/pkl-linux-amd64
chmod +x pkl
sudo mv pkl /usr/local/bin/

# macOS Apple Silicon (binary)
curl -L -o pkl https://github.com/apple/pkl/releases/latest/download/pkl-macos-aarch64
chmod +x pkl
sudo mv pkl /usr/local/bin/

# Verify
pkl --version

IDE Support

# VS Code extension
code --install-extension apple.pkl-vscode

# IntelliJ plugin available in JetBrains Marketplace

Core Language

Basic Values

// strings
name = "Pkl"
greeting = "Hello, \(name)!"   // String interpolation
multiline = """
  This is a
  multi-line string
  """

// Numbers
count = 42
price = 19.99
hex = 0xFF

// Boolean
active = true
disabled = false

// Null
optional = null

// Duration
timeout = 30.s
interval = 5.min
ttl = 24.h

// Data size
maxMemory = 512.mb
diskSize = 100.gb

Type System

TypeExampleDescription
String"hello"Text value
Int42Integer
Float3.14Floating point
BooleantrueTrue/false
Duration30.sTime duration
DataSize512.mbData size
ListList(1, 2, 3)Ordered collection
SetSet(1, 2, 3)Unique collection
MapMap("a", 1)Key-value pairs
ListingListing blockOrdered elements
MappingMapping blockNamed entries
DynamicDynamic blockFlexible structure
NullnullAbsence of value
UnionString|IntEither type

Objects and Classes

// Basic object
server {
  host = "localhost"
  port = 8080
  debug = false
}

// Class definition
class Server {
  host: String
  port: Int(isBetween(1, 65535))
  debug: Boolean = false
  protocol: "http"|"https" = "https"
}

// Instantiate
production: Server = new {
  host = "api.example.com"
  port = 443
  protocol = "https"
}

// Class with methods
class Rectangle {
  width: Float
  height: Float
  
  function area(): Float = width * height
  function perimeter(): Float = 2 * (width + height)
}

// Inheritance
class WebServer extends Server {
  maxConnections: Int = 1000
  corsOrigins: Listing<String>
}

Collections

// Listing (ordered, like arrays)
ports: Listing<Int> = new {
  8080
  8443
  9090
}

// Mapping (key-value, like maps)
labels: Mapping<String, String> = new {
  ["app"] = "myservice"
  ["env"] = "production"
  ["team"] = "platform"
}

// List operations
numbers = List(1, 2, 3, 4, 5)
doubled = numbers.map((n) -> n * 2)
evens = numbers.filter((n) -> n % 2 == 0)
sum = numbers.fold(0, (acc, n) -> acc + n)

Control Flow

// Conditional
environment = "production"
logLevel = if (environment == "production") "warn" else "debug"

// Null coalescing
username = optionalName ?? "anonymous"

// When expression
tier = when {
  users < 100 -> "starter"
  users < 1000 -> "growth"
  users < 10000 -> "business"
  else -> "enterprise"
}

// For generator (in listings)
ports: Listing<Int> = new {
  for (i in IntSeq(8080, 8085)) {
    i
  }
}

// Spread operator
base = new Listing { 1; 2; 3 }
extended = new Listing {
  ...base
  4
  5
}

Functions and Lambdas

// Local function
local function formatHost(host: String, port: Int): String =
  "\(host):\(port)"

serverUrl = formatHost("localhost", 8080)

// Lambda
transform = (x: Int) -> x * 2
result = transform.apply(21) // 42

// Higher-order
services: Listing<String> = new {
  "web"
  "api"
  "worker"
}

urls = services.map((s) -> "https://\(s).example.com")

Validation and Constraints

class DatabaseConfig {
  host: String(!isEmpty)
  port: Int(isBetween(1, 65535))
  name: String(matches(Regex("[a-z][a-z0-9_]*")))
  maxConnections: Int(isPositive) = 10
  sslMode: "disable"|"require"|"verify-full" = "require"
  
  // Custom validation
  connectionTimeout: Duration(this >= 1.s && this <= 60.s) = 10.s
}

class AppConfig {
  environment: "development"|"staging"|"production"
  debug: Boolean = (environment == "development")
  
  database: DatabaseConfig
  
  // Cross-field validation
  hidden replicas: Int(this >= 1) = if (environment == "production") 3 else 1
}

Modules and Imports

// config/base.pkl
module config.base

class ServerConfig {
  host: String
  port: Int
  replicas: Int = 1
}

server: ServerConfig
// config/production.pkl
amends "base.pkl"

server {
  host = "api.example.com"
  port = 443
  replicas = 3
}
// config/main.pkl
import "base.pkl"
import "package://pkg.pkl-lang.org/pkl-pantry/pkl.toml@1.0.0#/toml.pkl"

// Use imported module
myServer: base.ServerConfig = new {
  host = "localhost"
  port = 8080
}

Output Formats

CLI Commands

CommandDescription
pkl eval config.pklEvaluate and print Pkl output
pkl eval -f json config.pklOutput as JSON
pkl eval -f yaml config.pklOutput as YAML
pkl eval -f xml config.pklOutput as XML
pkl eval -f plist config.pklOutput as Property List
pkl eval -f properties config.pklOutput as Java Properties
pkl eval -o output.json config.pklWrite to file
pkl eval -m . config.pklMulti-file output
pkl test tests/Run Pkl tests
pkl project packageCreate distributable package

Generating JSON

# Generate JSON
pkl eval -f json config.pkl
// config.pkl
server {
  host = "localhost"
  port = 8080
  features = new Listing {
    "auth"
    "logging"
    "metrics"
  }
}

Output:

{
  "server": {
    "host": "localhost",
    "port": 8080,
    "features": ["auth", "logging", "metrics"]
  }
}

Generating YAML

pkl eval -f yaml config.pkl

Generating Multiple Files

// k8s.pkl
output {
  files {
    ["deployment.yaml"] = new YamlRenderer { value = deployment }
    ["service.yaml"] = new YamlRenderer { value = service }
    ["configmap.yaml"] = new YamlRenderer { value = configMap }
  }
}

Configuration

Project File (PklProject)

// PklProject
amends "pkl:Project"

package {
  name = "myconfig"
  version = "1.0.0"
  baseUri = "package://example.com/myconfig"
  packageZipUrl = "https://example.com/myconfig/\(version)/myconfig@\(version).zip"
}

dependencies {
  ["pkl-pantry"] = import("package://pkg.pkl-lang.org/pkl-pantry/pkl.toml@1.0.0")
}

Code Generation

# Generate Java classes
pkl-codegen-java config.pkl -o src/main/java

# Generate Kotlin classes
pkl-codegen-kotlin config.pkl -o src/main/kotlin

# Generate Swift structs
pkl-codegen-swift config.pkl -o Sources/Config

# Generate Go structs
pkl-gen-go config.pkl -o internal/config

Advanced Usage

Kubernetes Configuration

// k8s/deployment.pkl
import "package://pkg.pkl-lang.org/pkl-pantry/k8s@1.0.0#/api/apps/v1/Deployment.pkl"

deployment: Deployment = new {
  metadata {
    name = "myapp"
    namespace = "production"
    labels {
      ["app"] = "myapp"
      ["version"] = "1.0.0"
    }
  }
  spec {
    replicas = 3
    selector {
      matchLabels {
        ["app"] = "myapp"
      }
    }
    template {
      metadata {
        labels {
          ["app"] = "myapp"
        }
      }
      spec {
        containers {
          new {
            name = "myapp"
            image = "myapp:1.0.0"
            ports {
              new { containerPort = 8080 }
            }
            resources {
              requests {
                ["cpu"] = "100m"
                ["memory"] = "128Mi"
              }
              limits {
                ["cpu"] = "500m"
                ["memory"] = "512Mi"
              }
            }
          }
        }
      }
    }
  }
}

Testing

// tests/config_test.pkl
amends "pkl:test"

import "../config.pkl"

facts {
  ["server port is valid"] {
    config.server.port > 0
    config.server.port < 65536
  }
  ["production has replicas"] {
    config.server.replicas >= 3
  }
}

examples {
  ["default config"] {
    new config.ServerConfig {
      host = "localhost"
      port = 8080
    }
  }
}
pkl test tests/

Template Patterns

// templates/service.pkl
abstract class MicroService {
  name: String
  port: Int
  replicas: Int = 1
  environment: Mapping<String, String> = new {}

  // Computed properties
  hidden fullName: String = "svc-\(name)"
  hidden url: String = "http://\(fullName):\(port)"
}

// services.pkl
import "templates/service.pkl"

api = new MicroService {
  name = "api"
  port = 8080
  replicas = 3
  environment {
    ["LOG_LEVEL"] = "info"
    ["DB_HOST"] = "postgres"
  }
}

worker = new MicroService {
  name = "worker"
  port = 9090
  environment {
    ["QUEUE_URL"] = "redis://redis:6379"
  }
}

Troubleshooting

ProblemSolution
Type validation errorCheck constraints on class properties; use pkl eval to see errors
Import not foundCheck module URI; run pkl project resolve for packages
Circular dependencyRestructure modules; use amends for inheritance chains
Output format wrongSpecify format with -f json, -f yaml, etc.
IDE not showing errorsInstall Pkl VS Code extension; check language server is running
Code generation failsEnsure classes have explicit types; check codegen plugin version
Memory issues with large configsSplit into smaller modules; use lazy evaluation
Package resolution failsRun pkl project resolve; check PklProject dependencies