Skip to content

Foam Queries

Foam Queries let you show dynamic lists, tables, and counts of notes inside the Markdown preview.

Use them when you want a note to answer questions such as “show my research notes”, “list notes linked to this topic”, or “count notes in this folder”.

For static note embeds, see Note Embeds.

Use a foam-query code block:

```foam-query
filter: "#research"
sort: title ASC
limit: 10
```

This renders a list of matching notes in the preview. Query results update as your workspace changes.

If you omit filter, Foam searches all notes:

```foam-query
format: count
```
  • filter: choose which notes to include
  • select: choose which fields to display. Default: title and path
  • sort: sort results, for example title ASC or backlink-count DESC. Include the sort field in select, otherwise it will not be available after projection.
  • limit: show only the first n matches
  • offset: skip the first n matches
  • format: render as list, table, or count

When you select more than one field, Foam renders a table by default.

Use these shortcuts for common cases:

  • "#tag": notes with that tag
  • "[[note-id]]": notes linked to or from that note (use the same identifier as in wikilinks, e.g. the filename without extension)
  • "/regex/": notes whose path matches the regular expression
  • "*": all notes

Example:

```foam-query
filter: "[[project-alpha]]"
```

Use YAML when you need more control:

```foam-query
filter:
and:
- tag: "#research"
- not:
path: "^/archive/"
select: [title, tags, backlink-count]
sort: title ASC
```

Supported filter keys:

  • tag: notes that have this tag (e.g. tag: "#research")
  • type: notes of this type (e.g. type: "daily-note")
  • path: notes whose path matches this regex (e.g. path: "^/projects/")
  • title: notes whose title matches this regex
  • links_to: notes that link to the given note identifier. Use "$current" to refer to the note containing the query
  • links_from: notes that are linked from the given note identifier. Use "$current" to refer to the note containing the query
  • jexl: a Jexl expression evaluated against each note, e.g. "resource.tags|length > 2". The expression has access to resource (with fields title, path, type, tags, properties, backlinks, outlinks) and the built-in transforms length, lower, upper. Note: Jexl uses == (not ===) and |length (not .length). The previous expression field is deprecated and no longer evaluated.
  • and, or, not: combine filters logically
  • expression: REMOVED. A JavaScript expression that used to be evaluated against each note. Replaced by jexl for security reasons; legacy queries match nothing
  • Use "$current" in links_to or links_from to query relative to the note containing the query block:
```foam-query
filter:
links_to: "$current"
```

You can select these fields:

  • title
  • path
  • type
  • tags
  • aliases
  • sections
  • blocks
  • properties (use properties.<name> to pick one)
  • backlink-count
  • outlink-count
  • body — the full note text (frontmatter removed, H1 title kept), rendered as markdown
  • content — same as body but without the H1 title; useful when the title is already shown in another column
  • section[Label] — the content of the named section (heading removed), rendered as markdown

Example table:

```foam-query
filter: "#research"
select: [title, tags, backlink-count]
sort: backlink-count DESC
format: table
```

To show the full text of each matching note, select body or content:

```foam-query
filter:
jexl: "resource.properties.status == 'to_ask'"
select: [title, body]
```

To show just a named section, use section[Label]. Labels may contain spaces:

```foam-query
filter:
jexl: "resource.properties.status == 'to_ask'"
format: table
select:
- title
- section[Question]
- properties.status
```

When you write select: as a block sequence (each field on its own line, prefixed with -), you can use section[My Label] directly. If you use the inline form select: [...], YAML treats [ and ] as collection delimiters, so quote the value: select: [title, 'section[My Label]'].

Section labels are matched case-sensitivelysection[Question] will not match a heading written ## question.

body, content, and section[...] aren’t supported in VS Code Web — you’ll see an inline warning in their place. Everything else works.

By default the table header shows the field expression. For section[Decision] and properties.Status the wrapper is stripped automatically so you see Decision and Status. To set an explicit label, use the object form for that entry:

```foam-query
filter: "#decisions"
format: table
select:
- title
- field: section[Decision]
label: Chosen Decision
- field: properties.Status
label: Question status
```

Plain strings and { field, label } objects can be mixed in the same select.

Use count when you only need the number of matches:

```foam-query
filter:
path: "^/projects/"
format: count
```

Use foam-query-js when YAML is not enough. JavaScript queries only run in a trusted workspace. In a trusted workspace, a foam-query-js block runs with the same permissions as the rest of VS Code — it can read and write files, make network requests, and run any code your editor can. Treat blocks the same way you’d treat any script you’d download and run: only enable a trusted workspace for notes you author or trust.

```foam-query-js
const recentResearch = foam.pages('#research')
.sortBy('title')
.limit(5)
.format('list');
render('Recent research notes:');
render(recentResearch);
```

foam.pages(filter?) returns a query builder. Omit the filter to include all notes.

foam.current is the URI of the note containing the query. Use it to write queries that are relative to the current note:

```foam-query-js
// Show all notes that link to this note
render(foam.pages({ links_to: foam.current }).sortBy('title'));
```
```foam-query-js
// Show all notes that this note links to
render(foam.pages({ links_from: foam.current }).sortBy('title'));
```

foam.current is null if no document is active in the editor.

Available builder methods:

  • where(fn): keep only notes where fn(note) returns true, e.g. .where(n => n.tags.includes('draft'))
  • sortBy(field, direction?): sort by field, direction is 'asc' (default) or 'desc'
  • limit(n): return at most n results
  • offset(n): skip the first n results
  • select(fields): project to the given fields. Each entry can be a plain string (e.g. 'title') or an object { field, label } to customise the table column header, e.g. .select(['title', { field: 'properties.Status', label: 'State' }])
  • format(fmt): set the output format ('list', 'table', or 'count')
  • toArray(): return results as a plain array for use in custom logic

Call render(...) to show output in the preview. You can pass a query builder or a plain string.

Using .where together with body/content/section[...] is slow on large workspaces — narrow your results with a regular foam-query filter first when you can.

  • foam-query-js requires a trusted workspace. In a trusted workspace it runs with full editor permissions — no internal sandbox. If you don’t trust the source of your notes, keep the workspace untrusted.
  • jexl filters run in any workspace — Jexl is sandboxed by language design (no host globals, no file system, no network).
  • Queries render in Markdown preview, not directly in the editor.
  • Query results link back to the matching notes.
Published with Foam