nickel

Installation
SKILL.md

Nickel Configuration Language

Nickel is a configuration language designed to automate generation of static configuration files (JSON, YAML, TOML, XML). It combines gradual typing with runtime contracts to provide both static checking for complex logic and flexible validation for configuration data.

When to Use This Skill

Activate this skill when:

  • Defining typed configuration schemas with validation contracts
  • Validating YAML/JSON/TOML files against Nickel contract definitions
  • Merging multiple configuration files using Nickel's merge semantics
  • Converting between configuration formats (JSON ↔ YAML ↔ TOML)
  • Generating type-safe configurations from a single Nickel source
  • Creating reusable configuration templates with metadata
  • Building configuration pipelines with validation and transformation

Key Concepts

Gradual Typing

Nickel supports both static and dynamic typing. Mix typed and untyped code within the same configuration:

  • Typed regions: Functions with static type checking for complex logic
  • Dynamic regions: Flexible configuration values validated at runtime with contracts
  • Types are optional—choose when to use them

Contracts

Contracts are runtime assertions that validate values satisfy specific properties. They act as schemas:

  • Built-in contracts: Number, String, Bool, Dyn, Array, record contracts
  • Custom contracts: Functions that check predicates and return values or errors
  • Composition: Combine multiple contracts with boolean logic (std.contract.one_of, std.contract.all_of)
  • Metadata: Contracts attach to fields using the | operator

Merge System

The & operator merges records with symmetric semantics:

  • Symmetric merging: Both sides contribute to the result
  • Recursive merging: Nested records merge recursively
  • Metadata composition: Documentation, defaults, and contracts merge together
  • Priorities and defaults: Control override behavior with metadata

Installation

Using mise

Add to mise.toml:

[tools]
nickel = "latest"

[tasks.validate-config]
script = "nickel eval config.ncl"

[tasks.export-config]
script = "nickel export --format json config.ncl"

Using Cargo

cargo install nickel-lang-cli

CLI Usage

Evaluate Nickel Files

nickel eval config.ncl
nickel eval config.ncl --output result.json

Export to Different Formats

# Export to JSON
nickel export config.ncl --format json

# Export to YAML
nickel export config.ncl --format yaml

# Export to TOML
nickel export config.ncl --format toml

# Merge and export
nickel export base.json overrides.ncl --format yaml

Format Code

nickel format config.ncl
nickel format --check config.ncl  # Check without modifying

Interactive REPL

nickel repl

Basic Syntax

Records (Objects)

{
  field1 = "value",
  field2 = 42,
  nested = {
    key = "data",
  }
}

String Interpolation

let name = "app" in
let port = 8080 in
"Starting %{name} on port %{port}"

Arrays

let ports = [8000, 8001, 8002] in
ports

Functions

let add = fun x y => x + y in
let result = add 2 3 in
result

Let Bindings

let base_port = 8000 in
let debug = true in
{
  port = base_port,
  debug = debug,
}

Contracts and Validation

Applying Contracts with the Pipe Operator

Attach contracts to fields using |:

{
  port | Number = 8080,
  name | String = "my-service",
  debug | Bool = false,
}

Built-in Contracts

Nickel provides contracts for basic types:

# Number contract
value | Number

# String contract
value | String

# Boolean contract
value | Bool

# Dyn (dynamic) contract - never fails
value | Dyn

# Array contract
items | Array Number  # Array of numbers

# Record contract
config | {port: Number, host: String}

Custom Contracts

Define contracts as functions that validate and return values:

# Port number validation (1-65535)
let Port = std.contract.from_predicate (
  fun x => x >= 1 && x <= 65535
) in
{
  port | Port = 8080,
}

Contract with Error Messages

let LogLevel =
  fun label value =>
    if std.array.elem value ["debug", "info", "warn", "error"]
    then value
    else std.contract.blame label
in
{
  log_level | LogLevel = "info",
}

Combining Contracts

Use boolean contract combinators from std.contract:

# One of multiple contracts must match
value | std.contract.one_of [Contract1, Contract2]

# All contracts must match
value | std.contract.all_of [Contract1, Contract2]

# Negation
value | std.contract.not SomeContract

Importing and Validating Formats

Import YAML

let config = import "config.yaml" in
config

Import JSON

let data = import "data.json" in
data

Import TOML

let settings = import "settings.toml" in
settings

Apply Validation Contract to Import

let AppConfig = {
  port | Number,
  name | String,
  debug | Bool,
} in

(import "app.yaml") | AppConfig

Full Validation Workflow

# Define schema
let ConfigSchema = {
  app_name | String,
  port | Number,
  log_level | String,
  database = {
    host | String,
    port | Number,
  }
} in

# Import and validate
let imported = import "config.yaml" in
let validated = imported | ConfigSchema in

# Export as JSON
validated

Merge System

Basic Merge

let base = {
  app_name = "service",
  port = 8080,
} in

let overrides = {
  port = 443,
} in

# Result: port = 443 (overrides takes precedence)
base & overrides

Recursive Merge

let base = {
  database = {
    host = "localhost",
    port = 5432,
  }
} in

let env_overrides = {
  database = {
    host = "prod.example.com",
  }
} in

# Nested database.host overridden, port preserved
base & env_overrides

Defaults and Optional Fields

let defaults = {
  port | default = 8080,
  log_level | default = "info",
} in

let user_config = {
  port = 3000,
} in

defaults & user_config  # port = 3000, log_level = "info"

Anti-Fabrication Notes

When using Nickel configurations:

  • Verify contract behavior by running nickel eval file.ncl to confirm validation works as expected
  • Test import validation using nickel eval with actual data files to ensure format imports succeed
  • Validate merge results by examining output with nickel export --format json to confirm merge semantics
  • Check CLI flags against current Nickel version documentation, as CLI may have changed since this skill was written

Ecosystem Tools

json-schema-to-nickel

Convert JSON Schema specifications to Nickel contracts:

# Generates Nickel contract from JSON Schema
json-schema-to-nickel schema.json > schema.ncl

Topiary Integration

Nickel is Topiary's configuration language. Topiary's language configuration uses languages.ncl:

# Topiary language configuration with Nickel
{
  language = "rust",
  formatting_rules = {
    indent = 2,
  }
}

mise Task Examples

Define validation and export tasks in mise.toml:

[tasks.config:validate]
description = "Validate configuration against schema"
script = "nickel eval config.ncl --output /dev/null && echo 'Valid'"

[tasks.config:export-json]
description = "Export configuration as JSON"
script = "nickel export config.ncl --format json --output config.json"

[tasks.config:export-all]
description = "Export in all formats"
script = """
nickel export config.ncl --format json --output config.json
nickel export config.ncl --format yaml --output config.yaml
nickel export config.ncl --format toml --output config.toml
"""

Additional Resources

Related skills
Installs
3
GitHub Stars
18
First Seen
Mar 23, 2026