Queries

A query fence renders a live list, table, or component from Sources or Blocks that match. Results respect the reader's access.

```query
from: .
type: reference
sort:
  - by: meta.title
```

List two lessons

In a Quickstart project, create paths/lessons/contrast.lesson.md:

---
level: beginner
---

# Contrast

Compare a dark heading with a pale one.

---

<!--
id: practice
type: exercise
-->

## Compare two headings

Choose the heading you can read more easily.

Create paths/lessons/spacing.lesson.md:

---
level: beginner
---

# Spacing

Give a heading room above and below it.

[Review contrast](./contrast.lesson.md).

Create paths/lessons/index.md:

# Design lessons

```query
from: .
type: lesson
sort:
  - by: meta.title
```

Run bun run dev and open /lessons. The list contains Contrast, then Spacing, each linked to its lesson. Change Spacing's heading to “Breathing room”: the live list updates and sorts it first. Restore the heading for the examples below. Core's standard Plugins provide queries; no package is needed.

Show matching fields in a table

Replace the query in paths/lessons/index.md with:

```query
from: .
type: lesson
where:
  level: beginner
view:
  type: table
  columns: [meta.title, level]
```

The same two lessons appear with their title and beginner level. Change the filter to advanced to get no results. Queries only return readable Sources; a missing row may be an access boundary rather than an absent file.

Contract

KeyMeaning
querysources (default), blocks, or a plugin query such as plugin.name.
fromFolder to search. . is this folder; / is the root; ~/ is the Actor home.
typeSource or Block type.
whereMap of authored data fields to required values.
sortList of by fields with optional direction: asc or desc. meta.title sorts by title.
limitMaximum number of results.
hasRegistered prop name, or a list, that results must declare.
recursivefalse limits results to the from folder itself.
linksto: or from: a Source path, this.source, or this.block.
viewlist (default) or type: table with columns.
componentImported Markdown tag or authored Plugin tag that renders each result.

Component results

Create paths/lesson-cards.components.md:

<!-- componentName: lesson-entry -->

# Lesson entry

A compact link with the lesson's authored level.

```html
<article>
  <a href="{{ href }}">{{ title }}</a>
  <span>{{ level }}</span>
</article>
```

Replace paths/lessons/index.md with:

[@cards]: ../lesson-cards.components.md

# Design lessons

```query
from: .
type: lesson
sort:
  - by: meta.title
component: lesson-entry
```

The two results now render as linked lesson titles with their beginner level. The component and dataset are ordinary Sources you can inspect and edit. For a wrapper with slotted query children, see the complete bookcase and shelf components.

Result component attributes

Use component: book-card for an imported Markdown component, or an installed Plugin's authored tag such as component: x-books-card. Markdown result components can contain Plugin components.

Each result supplies string attributes from its scalar data, top-level fields, and metadata, in that order; later fields take precedence. For Source results, href is the public route and title comes from metadata.

Source and Block results also supply source, an exact canonical reference (including the fragment for a Block), and label, defaulting to the target's title. These let the same component render an individual rich reference or a query result. Relative target images resolve beside their owning Source. Named Plugin query results keep their own field meanings.

Query components prepare with each row's actual attributes, then render for the current viewer. Static export runs the same query and components anonymously. Actor-dependent results require a host that admits that Actor, including a local Actor in a browser publication.

Add these Blocks to paths/lessons/index.md after the previous example:

---

## Practice

```query
query: blocks
from: .
type: exercise
```

---

## Lessons that link to Contrast

```query
from: .
type: lesson
links:
  to: ./contrast.lesson.md
```

Practice links to Compare two headings inside Contrast; the link query lists Spacing, whose body links to Contrast. links.from instead selects targets linked from its anchor. Use this.source or this.block when the authoring Source or Block is the anchor. Keep exact Block ids stable when querying links to them.

View bindings

Use a named Markdown reference to read one Source through the current View:

[profile]: ~/profile.profile.md

Hello, {{ $profile.display-name ?? "Guest" }}.

<x-source-read name="profile">
  <p>{{ $profile.display-name }}</p>
  <slot name="fallback"><p>Add your profile to introduce yourself.</p></slot>
</x-source-read>

~/ resolves within the current Actor's Home. Relative references resolve beside their declaring Source; a fragment selects a Block. An unavailable or unreadable target uses the literal default or fallback slot. Only the selected slot renders and performs reads. A reference that is never consumed performs no read.

Bindings work in Markdown prose, literate HTML templates, and scalar HTML or component attributes. Shared compilation keeps the instruction; each admitted View resolves its own values and live dependencies. Returned scalar values are HTML-escaped. Missing fields and non-scalar values render empty. ?? supplies a string, number, or boolean only when the value is null or absent; false, 0, and empty text are preserved. Code examples, comments, and scripts remain literal. Bindings do not evaluate JavaScript, loop over arrays, or embed a Source body.

A typed query can be called directly:

{{ $input.responses(source=this.source, response="reflection").values.idea ?? "No saved idea" }}

Named arguments accept scalar literals, this.source, and this.block. A query binding is singular: no rows means unavailable, one row supplies the value, and multiple rows produce an authoring diagnostic. Binding declarations cannot use limit to hide ambiguity. Query fences remain the collection presentation form.

For reuse or an availability wrapper, put the same request in Source topmatter:

reads:
  answer:
    query: input.responses
    input:
      source: this.source
      response: reflection
{{ $answer.values.idea ?? "No saved idea" }}

<x-source-read name="answer">
  <p>{{ $answer.values.idea }}</p>
  <slot name="fallback"><p>Save your reflection first.</p></slot>
</x-source-read>

A Block inherits its Source's local reads. A Block entry replaces the whole inherited definition; false disables it. Representation selectors follow the represented Source or Block in layouts, query results, and rich references. Projected slots retain their caller's scope. Undefined names, invalid query inputs, duplicate declarations, and ambiguous results diagnose errors; they do not silently become missing data.

Repository-wide bindings

An installed Plugin can export a Source or query descriptor:

import { definePlugin } from "@pathmx/core"

export default definePlugin({
  id: "workshop",
  bindings: {
    profile: { source: "~/profile.profile.md" },
  },
})

This makes $profile available throughout the repository without local references. Exported names are unique and reserved: local reads and Markdown references cannot shadow them, and a binding cannot use an installed Plugin's id. Plugins supply declarative Source/query definitions, not callbacks or another state store. Unused exports perform no reads. Operation identifiers consistently use plugin.member for Queries, Actions, and Events; component tags retain their x-plugin-member HTML form.

Notes

  • pmx lint reports an invalid fence, an unknown component, or a links target that does not exist.
  • pmx query sources|blocks|plugin.name runs the same query from the terminal; see the CLI.

Call a named Plugin query

Server code passes a Plugin query's arguments inside input. With the Path Plugin installed and a readable /course.path, use:

const steps = await view.query("path.steps", {
  input: { path: "/course.path" },
})

Top-level request fields belong to Core's query envelope. Passing { path: ... } at the top level is rejected. The admitted View still controls what the query can read; changing input does not grant another Actor's authority.