---
title: "DCQL: The Developer's Guide to the Digital Credentials Query Language"
description: "A complete guide to DCQL (Digital Credentials Query Language). Learn the mental model, credential query anatomy, claim paths, trust filtering, and how credential_sets work."
stack: "Concepts (stack-agnostic, applies to both Community and Enterprise Stack)"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/concepts/data-exchange-protocols/dcql
generated: 2026-07-20
---
# DCQL: The Digital Credentials Query Language

**DCQL (Digital Credentials Query Language)** is the mechanism verifiers use inside an [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) request to express exactly which credentials a wallet should present, and in what combinations. It was introduced in OpenID4VP Draft 22 (October 2024) and is part of the final `v1.0` specification, replacing the older Presentation Exchange / Presentation Definition approach.

This guide explains how DCQL is structured, what each part does, and where the common implementation mistakes occur.

## The Mental Model

DCQL is the verifier's answer to one specific question: "out of the credentials the wallet holds, which ones — and in what combinations — can satisfy what I need from this transaction?"

There are only ever two layers of decision:

1. **A list of credential queries** — "I want a credential that looks like X."
2. **(Optional) logic over those queries** — "I need (A and B), or (A and C), but never just A on its own."

That is the entire shape of the language. Every detail in the specification hangs off those two ideas.

**Compared to Presentation Exchange** (which DCQL replaces), this is dramatically simpler. Presentation Exchange required JSON Schema-like constraint descriptors with JSONPath fragments and nested filter blocks. DCQL replaces that constraint-language sprawl with named fields, a single claim path syntax, and explicit AND/OR operators.

## The Top-Level Shape

Every DCQL query is an object with two possible keys:

```json
{
  "credentials": [ ... ],
  "credential_sets": [ ... ]
}
```

- `credentials` is **required** — your list of "I want a credential like X" entries.
- `credential_sets` is **optional** — AND/OR logic across those entries.

If you only provide `credentials` and no `credential_sets`, the wallet must satisfy every entry — an implicit AND. You add `credential_sets` only when you need OR, optionality, or alternative combinations.

**Note:**

This is where most implementations over-engineer. If the use case is "give me a PID and an employment credential," do not reach for `credential_sets`. Two entries in `credentials` already expresses that.

## Anatomy of a Credential Query

Each entry in `credentials` follows this structure:

```json
{
  "id": "digitalID",
  "format": "dc+sd-jwt",
  "meta": { "vct_values": ["https://issuer.demo.walt.id/draft13/digitalID"] },
  "claims": [ ... ],
  "claim_sets": [ ... ],
  "trusted_authorities": [ ... ],
  "require_cryptographic_holder_binding": true,
  "multiple": false
}
```

The required fields are `id`, `format`, and `meta`. Everything else is optional and each unlocks one specific capability.

| Field | Required | Description |
|---|---|---|
| `id` | Yes | Your handle for this query. The wallet keys its `vp_token` response by these ids. Must be unique within the request. |
| `format` | Yes | Credential format identifier: `dc+sd-jwt`, `mso_mdoc`, `jwt_vc_json`. |
| `meta` | Yes | Format-specific filter. The structure differs per format — see the [Format-Specific Gotchas](#format-specific-gotchas) section below. |
| `claims` | No | Limits which claims the wallet should disclose. Omitting this is not the same as requesting everything — see below. |
| `claim_sets` | No | Alternative combinations of claims within this single credential. |
| `trusted_authorities` | No | Restricts matches to credentials from specific trust anchors. |
| `require_cryptographic_holder_binding` | No | Defaults to `true`. Set `false` only when a key binding proof is genuinely not needed. |
| `multiple` | No | Defaults to `false`. Set `true` to allow the wallet to return more than one matching credential of the same type. |

The minimum viable query is exactly the three required fields and nothing else. The wallet returns the entire credential.

## Selective Disclosure: Claims and the Claims Path Pointer

Add a `claims` array to tell the wallet which fields you actually need:

```json
"claims": [
  { "path": ["given_name"] },
  { "path": ["family_name"] },
  { "path": ["age_over_18"] }
]
```

### The `path` syntax

`path` is an array, not a string. Each segment is one of:

- A **string** — an object key (e.g. `"address"`)
- An **integer** — an array index
- **`null`** — matches all elements at that array level

So `["address", "locality"]` means "the `locality` field inside the `address` object." This is the Claims Path Pointer defined in §7 of the spec. It is not JSONPath, it is not JSON Pointer — it is its own syntax and you will trip on it if you assume otherwise.

### Format affects path semantics

For JSON-based credentials (`dc+sd-jwt`, `jwt_vc_json`), paths walk the credential's claim structure as you'd expect.

For **mdoc**, the rule is explicit: the first segment is the **namespace**, the second is the **element identifier**, and that is the entire path — mdoc paths are always exactly two segments long. So for an mDL `given_name`, you write:

```json
{ "path": ["org.iso.18013.5.1", "given_name"] }
```

### What "no claims" means

If `claims` is absent, you are requesting no selectively-disclosable claims. For an SD-JWT VC, the wallet returns only the always-disclosed envelope and the key binding JWT. Omitting `claims` does **not** mean "give me everything."

## Value Matching with `values`

You can constrain a claim to a specific value:

```json
{ "path": ["nationality"], "values": ["AT", "DE"] }
```

The wallet should return the claim only if its type and value match at least one of the listed values. Strings, integers, and booleans are accepted.

**Info:**

Value matching leaks information about claim non-possession. If the wallet refuses because the nationality is not in the list, the verifier learns the holder's nationality is something else.

For mdoc credentials, CBOR values must be JSON-converted (per RFC 8949 §6.1) before matching. That conversion is not always lossless — date types, byte strings, and similar types can behave unexpectedly. Do not rely on value matching for mdoc data types beyond the simple primitives.

## `claim_sets` — Alternative Claim Combinations Within One Credential

`claim_sets` answers the question: "which combinations of claims would satisfy me from this single credential?"

```json
"claims": [
  { "id": "gn",     "path": ["given_name"] },
  { "id": "fn",     "path": ["family_name"] },
  { "id": "dob",    "path": ["birth_date"] },
  { "id": "over18", "path": ["age_over_18"] }
],
"claim_sets": [
  ["gn", "fn", "over18"],
  ["gn", "fn", "dob"]
]
```

Read this as: "I would accept either {given_name, family_name, age_over_18} or {given_name, family_name, birth_date}." The order of the sets expresses the verifier's **preference** — the wallet should return the first option it can satisfy, but is not strictly required to. Put the set you most prefer first: if you want to signal a preference for minimum disclosure, list the smaller or more privacy-preserving set earlier.

Once `claim_sets` is used, the `id` field on each entry in `claims` becomes mandatory — the sets reference claims by those ids.

`claim_sets` is for **alternative claim combinations within one credential**. It is not a mechanism for making individual claims optional. DCQL has no concept of an optional claim by design.
If you need to express an optional claim, there are two patterns:
1. **Within a single credential query** — use `claim_sets` with the "richer" set first and the minimal set as a fallback. The wallet will return the richer set if it can satisfy it, and fall back to the minimal one if not. This is not true optionality (the user has no choice), but it gracefully handles credentials that may or may not contain a given claim.
2. **Across two credential queries** — model the optional claim as a separate credential query inside an optional `credential_set` (see the next section). This is the more explicit pattern and ties the optionality to a declared purpose.

## Multiple Credentials — Implicit AND

Two entries in `credentials` with no `credential_sets`:

```json
{
  "credentials": [
    { "id": "pid",        "format": "dc+sd-jwt", "meta": { ... }, "claims": [ ... ] },
    { "id": "employment", "format": "dc+sd-jwt", "meta": { ... }, "claims": [ ... ] }
  ]
}
```

Both must be satisfied. The `vp_token` in the response will be an object with two keys — `pid` and `employment` — each holding a presentation. The keys are exactly the `id` values you chose in the query, so pick readable names.

By default, each key maps to a single presentation. If you set `multiple: true` on a credential query, the wallet may return an array of presentations for that key instead.

## `credential_sets` — Explicit Logic Across Credentials

`credential_sets` is where you express OR, optionality, and alternative credential combinations. Each set has two properties:

```json
"credential_sets": [
  {
    "options": [ ["pid"], ["mdl"] ],
    "required": true
  }
]
```

- `options` is an array of arrays. Each inner array is one acceptable combination of credential ids. The wallet must satisfy at least one inner array in full.
- `[["pid"], ["mdl"]]` reads as "(pid) OR (mdl)."
- To express "(pid AND id_card) OR (mdl)" you write `[["pid", "id_card"], ["mdl"]]`.
- `required` defaults to `true`. Setting it to `false` makes the entire set optional.

**Modelling optional credentials** — "give me a PID, and if you happen to have a diploma, include it":

```json
"credential_sets": [
  { "options": [["pid"]] },
  { "required": false, "options": [["diploma"]] }
]
```

**Note:**

When you use `credential_sets`, the sets become the sole source of truth for what is required. The `credentials` array defines the universe of possible credential shapes — their format, claims, and constraints — but it is the sets that determine what actually gets requested. Entries in `credentials` that are not referenced by any set are inert: the wallet has no instruction to return them.

## Trust Framework Filtering with `trusted_authorities`

For EUDI and other regulated ecosystems, `trusted_authorities` is where policy constraints end up:

```json
"trusted_authorities": [
  { "type": "etsi_tl", "values": ["https://eu-lotl.example/lotl.xml"] }
]
```

Three authority types are defined:

| Type | Description |
|---|---|
| `aki` | Authority Key Identifier, base64url-encoded. The values are the key identifiers of the trusted issuer CA certificates (their Subject Key Identifier). A credential matches if its certificate chain contains a certificate whose AKI extension's keyIdentifier component references one of those trusted CAs. Use when trusting a specific issuer CA key directly. |
| `etsi_tl` | An ETSI Trusted List URL (TS 119 612). The wallet walks the LOTL/TL cascade and matches if any cert in the credential's chain appears on the referenced trust list. The natural fit for QTSP-issued credentials under eIDAS. |
| `openid_federation` | An Entity Identifier. The wallet must be able to construct a valid trust chain from the credential's issuer up to that entity. |

**Note:**

The spec is explicit: `trusted_authorities` is a **data-minimization hint to the wallet**, not a verifier-side trust check. The verifier is still responsible for independently verifying issuer trust during validation. Do not treat this field as a substitute for that check.

## Holder Binding

```json
"require_cryptographic_holder_binding": false
```

This field defaults to `true`. Set it to `false` only when the credential genuinely does not require cryptographic key binding — cinema tickets, biometric-bound credentials (mDL portrait), or claims-bound diplomas.

There are real consequences when you set it to `false`:

- The Verifiable Presentation will not include a key binding proof, making the credential replayable.
- Nonce-based replay protection no longer applies. The spec requires `state` in the Authorization Request with at least 128 bits of entropy as a substitute.

## Format-Specific Gotchas — The `meta` Block

The `meta` object has a different shape for each credential format.

### SD-JWT VC (`dc+sd-jwt`)

```json
"meta": { "vct_values": ["https://issuer.demo.walt.id/draft13/EducationalID"] }
```

`vct_values` is an **array of strings**. The wallet matches if the credential's `vct` claim equals exactly one of the values listed.

### mdoc (`mso_mdoc`)

```json
"meta": { "doctype_value": "org.iso.18013.5.1.mDL" }
```

`doctype_value` is a **single string**, not an array. The wallet matches against the credential's MSO doctype.

### W3C VCDM (`jwt_vc_json`)

```json
"meta": {
  "type_values": [
    ["VerifiableCredential", "UniversityDegreeCredential"]
  ]
}
```

`type_values` is an **array of arrays of strings**. The outer array is OR; the inner array is AND-over-types. So `[["VerifiableCredential", "UniversityDegreeCredential"]]` means "the credential's `type` array contains both of these values." This is easy to get wrong — a single-level array is a common mistake.

## Request credentials using DCQL with walt.id

The Community Stack verifier API accepts DCQL queries directly inside an OpenID4VP presentation request. Pass your `dcql_query` object in the request body when initiating a verification session.

- [Verify a Credential (OID4VP)](https://docs.walt.id/community-stack/verifier/credential-verification/vc-oid4vc.md) — Initiate a presentation request with a `dcql_query` using the community verifier API.
- [Verify an SD-JWT VC](https://docs.walt.id/community-stack/verifier/credential-verification/sd-jwt-vc-oid4vc.md) — SD-JWT VC-specific verification flow showing how DCQL claim paths map to disclosed fields.

## Related concepts

- [OpenID4VP](https://docs.walt.id/concepts/data-exchange-protocols/openid4vp.md) — The presentation protocol that carries the `dcql_query` inside an Authorization Request.
- [DC API](https://docs.walt.id/concepts/data-exchange-protocols/dc-api.md) — Browser-native credential presentation, also using `dcql_query` as its query format.
- [HAIP](https://docs.walt.id/concepts/data-exchange-protocols/haip.md) — The high-assurance profile that also uses DCQL and which is imporant for eIDAS 2.q
