Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?


Failed to load latest commit information.
Latest commit message
Commit time


kdl is a "document-oriented" parser and API for the KDL Document Language, a node-based, human-friendly configuration and serialization format. Unlike serde-based implementations, this crate preserves formatting when editing, as well as when inserting or changing values with custom formatting. This is most useful when working with human-maintained KDL files.

You can think of this crate as toml_edit, but for KDL.

If you don't care about formatting or programmatic manipulation, you might check out knuffel or kaydle instead for serde (or serde-like) parsing.


use kdl::KdlDocument;

let doc_str = r#"
hello 1 2 3

world prop="value" {
    child 1
    child 2

let doc: KdlDocument = doc_str.parse().expect("failed to parse KDL");

    vec![&1.into(), &2.into(), &3.into()]

    doc.get("world").map(|node| &node["prop"]),

// Documents fully roundtrip:
assert_eq!(doc.to_string(), doc_str);

Controlling Formatting

By default, everything is created with default formatting. You can parse items manually to provide custom representations, comments, etc:

let node_str = r#"
  // indented comment
  "formatted" 1 /* comment */ \

let mut doc = kdl::KdlDocument::new();

assert_eq!(&doc.to_string(), node_str);

KdlDocument, KdlNode, KdlEntry, and KdlIdentifier can all be parsed and managed this way.

Query Engine

kdl includes a query engine for KQL, which lets you pick out nodes from a document using a CSS Selectors-style syntax.

Queries can be done from either a KdlDocument or a KdlNode, with mostly the same semantics.

use kdl::KdlDocument;

let doc = r#"
a {
    b 1
    c 2
    d 3 {
        e prop="hello"
"#.parse::<KdlDocument>().expect("failed to parse KDL");

let results = doc.query("a > b").expect("failed to parse query");
assert_eq!(results, Some(&doc.nodes()[0].children().unwrap().nodes()[0]));

let results = doc.query_get("e", "prop").expect("failed to parse query");
assert_eq!(results, Some(&"hello".into()));

let results = doc.query_get_all("a > []", 0).expect("failed to parse query").collect::<Vec<_>>();
assert_eq!(results, vec![&1.into(), &2.into(), &3.into()]);

Error Reporting

KdlError implements miette::Diagnostic and can be used to display detailed, pretty-printed diagnostic messages when using miette::Result and the "fancy" feature flag for miette:

# Cargo.toml
miette = { version = "x.y.z", features = ["fancy"] }
fn main() -> miette::Result<()> {
    "foo 1.".parse::<kdl::KdlDocument>()?;

This will display a message like:

  × Expected valid value.
 1 │ foo 1.
   ·     ─┬
   ·      ╰── invalid float
  help: Floating point numbers must be base 10, and have numbers after the decimal point.



Multiple properties with the same name are allowed, and all duplicated will be preserved, meaning those documents will correctly round-trip. When using node.get()/node["key"] & company, the last property with that name's value will be returned.


KDL itself does not specify a particular representation for numbers and accepts just about anything valid, no matter how large and how small. This means a few things:

  • Numbers without a decimal point are interpreted as [u64].
  • Numbers with a decimal point are interpreted as [f64].
  • Floating point numbers that evaluate to [f64::INFINITY] or [f64::NEG_INFINITY] or NaN will be represented as such in the values, instead of the original numbers.
  • A similar restriction applies to overflowed [u64] values.
  • The original representation of these numbers will be preserved, unless you [KdlDocument::fmt] in which case the original representation will be thrown away and the actual value will be used when serializing.


The code in this repository is covered by the Apache-2.0 License.