foam Documentation

A personal knowledge management and sharing system for VSCode

RAW Doc

Technical Documentation: foambubble/foam

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Index (`docs/index.md`)

What is Foam?

Foam is a personal knowledge management system built on Visual Studio Code and GitHub. It helps you organize research, create discoverable notes, and publish your knowledge.

Key Features

  • Wikilinks - Connect thoughts with [[double bracket]] syntax
  • Embeds - Include content from other notes with ![[note]] syntax
  • Backlinks - Automatically discover connections between notes
  • Graph visualization - See your knowledge network visually
  • Daily notes - Capture timestamped thoughts
  • Templates - Standardize note creation
  • Tags - Organize and filter content

Why Choose Foam?

  • Free and open source - No subscriptions or vendor lock-in
  • Own your data - Notes stored as standard Markdown files
  • VS Code integration - Leverage powerful editing and extensions
  • Git-based - Version control and collaboration built-in

Foam is like a bathtub: What you get out of it depends on what you put into it.

New! Join Foam community Discord for users and contributors!

text
<a class="github-button" href="https://github.com/foambubble/foam" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star foambubble/foam on GitHub">Star</a>
<a class="github-button" href="https://github.com/foambubble/foam-template" data-icon="octicon-repo-template" data-size="large" aria-label="Use this template foambubble/foam-template on GitHub">Use this template</a>

Table of Contents

How do I use Foam?

Foam helps you create relationships between thoughts and information through:

  1. Atomic notes - Write focused markdown documents on single topics
  2. Wikilinks - Connect ideas with [[double bracket]] syntax
  3. Backlinks - Discover unexpected connections between notes
  4. Graph visualization - See your knowledge network visually

Success with Foam depends on consistent note-taking and linking habits.

What's in a Foam?

Foam combines existing tools:

  1. VS Code - Enhanced with [recommended-extensions] optimized for knowledge management
  2. GitHub - Version control, backup, and collaboration
  3. Static site generators - Publish to GitHub Pages, Netlify, or Vercel

This documentation was created using Foam.

Getting started

Requirements: GitHub account and Visual Studio Code

  1. Create repository - Use the foam-template to generate a new repository

    Use this template

  2. Clone and open - Clone locally and open the folder in VS Code

  3. Install extensions - Click "Install all" when prompted for recommended extensions

  4. Configure - Edit .vscode/settings.json for your preferences

Next steps:

Features

Foam leverages VS Code and [recommended-extensions] to provide:

  • Wikilinks with autocomplete and navigation
  • Backlinks panel showing connections
  • Graph visualization of your knowledge network
  • Daily notes with templates and snippets
  • Tag system for organization
  • Publishing to static sites

Explore [recipes] for usage patterns and workflows.

Contributing

Foam is an evolving project and we welcome contributions:

Thanks and attribution

Jani Evรคkallio๐Ÿ’ป ๐Ÿ“– Joe Previte๐Ÿ’ป ๐Ÿ“– Riccardo๐Ÿ’ป ๐Ÿ“– Janne Ojanaho๐Ÿ’ป ๐Ÿ“– Paul Shen๐Ÿ“– coffenbacher๐Ÿ“– Mathieu Dutour๐Ÿ“–
Michael Hansen๐Ÿ“– David Nadlinger๐Ÿ“– Fernando๐Ÿ“– Juan Gonzalez๐Ÿ“– Louie Christie๐Ÿ“– Sandro๐Ÿ“– Simon Knott๐Ÿ“–
Steven๐Ÿ“– Tim๐Ÿ“– Saurav Khdoolia๐Ÿ“– Ankit Tiwari๐Ÿ“– โš ๏ธ ๐Ÿ’ป Ayush Baweja๐Ÿ“– TaiChi-IO๐Ÿ“– **Juan F Gonzalez **๐Ÿ“–
Sanket Dasgupta๐Ÿ“– ๐Ÿ’ป Nicholas Stafie๐Ÿ“– Francis Hamel๐Ÿ’ป digiguru๐Ÿ’ป ๐Ÿ“– CHIRAG SINGHAL๐Ÿ’ป Jonathan Carter๐Ÿ“– Julian Elve๐Ÿ“–
Thomas Koppelaar๐Ÿ’ฌ ๐Ÿ’ป ๐Ÿ““ Akshay๐Ÿ’ป John Lindquist๐Ÿ“– Ashwin Ramaswami๐Ÿ“– Claudio Canales๐Ÿ“– vitaly-pevgonen๐Ÿ“– Dmitry Shemetov๐Ÿ“–
hooncp๐Ÿ“– Martin Laws๐Ÿ“– Sean K Smith๐Ÿ’ป Kevin Neely๐Ÿ“– Arief Rahmansyah๐Ÿ“– Vishesh Handa๐Ÿ“– Hitesh Kumar๐Ÿ“–
Spencer Woo๐Ÿ“– ingalless๐Ÿ’ป ๐Ÿ“– Josรฉ Duarte๐Ÿ’ป ๐Ÿ“– Yenly๐Ÿ“– hikerpig๐Ÿ’ป Sigfried Gold๐Ÿ“– Tristan Sokol๐Ÿ’ป
Danil Rodin๐Ÿ“– Scott Williams๐Ÿ“– jackiexiao๐Ÿ“– John B Nelson๐Ÿ“– Asif Mehedi๐Ÿ“– Tan Li๐Ÿ’ป Shauna Gordon๐Ÿ“–
Mike Cluck๐Ÿ’ป Brandon Pugh๐Ÿ’ป Max Davitt๐Ÿ“– Brian Anglin๐Ÿ“– elswork๐Ÿ“– lรฉon h๐Ÿ’ป Nikhil Nygaard๐Ÿ“–
Mark Dixon๐Ÿ’ป Joel James๐Ÿ’ป Hashiguchi Ryo๐Ÿ“– Michael Overmeyer๐Ÿ’ป Derrick Qin๐Ÿ“– Omar Lรณpez๐Ÿ“– Robin King๐Ÿ’ป
**Dheepak **๐Ÿ“– Daniel VG๐Ÿ“– Barabas๐Ÿ’ป Engincan VESKE๐Ÿ“– Paul de Raaij๐Ÿ’ป Scott Bronson๐Ÿ“– Rafael Riedel๐Ÿ“–
Pearcekieser๐Ÿ“– Owen Young๐Ÿ“– ๐Ÿ–‹ Prashanth Subrahmanyam๐Ÿ“– Jonas SPRENGER๐Ÿ’ป Paul๐Ÿ“– Ikko Ashimine๐Ÿ“– memeplex๐Ÿ’ป
AndreiD049๐Ÿ’ป Yan๐Ÿ“– Jim Tittsler๐Ÿ“– Malcolm Mielle๐Ÿ“– Veesar๐Ÿ“– bentongxyz๐Ÿ’ป Brian DeVries๐Ÿ’ป
**Clifford Fajardo **๐Ÿ”ง Chris Usick๐Ÿ’ป Joe DeCock๐Ÿ’ป Drew Tyler๐Ÿ“– Lauviah0622๐Ÿ’ป Josh Dover๐Ÿ’ป Phil Helm๐Ÿ“–
Larry Li๐Ÿ’ป Joe Taber๐Ÿ“– Woosuk Park๐Ÿ“– Daniel Murphy๐Ÿ’ป Dominic D๐Ÿ’ป luca๐Ÿ“– Lloyd Jackman๐Ÿ“–
sn3akiwhizper๐Ÿ“– jonathan berger๐Ÿ“– Daniel Wang๐Ÿ’ป Liu YongLiang๐Ÿ“– Scott Akerman๐Ÿ’ป Jim Graham๐Ÿ’ป Zhizhen He๐Ÿ”ง
Tony Cheneau๐Ÿ“– Nicholas Latham๐Ÿ’ป Tomochika Hara๐Ÿ“– Daniel Carosone๐Ÿ“– Miguel Angel Bruni Montero๐Ÿ’ป **Kevin Walsh **๐Ÿ“– Xinglan Liu๐Ÿ’ป
Thomas Hegghammer๐Ÿ“– Piotr Mrzygล‚osz๐Ÿ“– Mark Schaver๐Ÿ“– Nathan Layman๐Ÿ’ป Emmanuel Ferdman๐Ÿ“– Tenormis๐Ÿ’ป David Jones๐Ÿ“–
S. Jacob Powell๐Ÿ’ป Davi Figueiredo๐Ÿ“– CT Hall๐Ÿ’ป meestahp๐Ÿ’ป qwloh๐Ÿ“– Ryan N๐Ÿ’ป Nils Henrik Hals๐Ÿ’ป
Florian Meyer๐Ÿ’ป Sherlock290๐Ÿ’ป

Foam was inspired by Zettelkasten methodology.

Foam builds on Visual Studio Code, GitHub, and our [recommended-extensions].

License

Foam is licensed under the MIT license.


User Index (`docs/user/index.md`)

Using Foam

Foam is a personal knowledge management system built on Visual Studio Code and GitHub. It helps you organize research, create discoverable notes, and publish your knowledge.

See also [frequently-asked-questions].

Key Features

  • Wikilinks - Connect thoughts with [[double bracket]] syntax
  • Block anchors - Link or embed specific paragraphs, list items, and headings with [[note#^id]]
  • Embeds - Include content from other notes with ![[note]] syntax
  • Backlinks - Automatically discover connections between notes
  • Graph visualization - See your knowledge network visually
  • Daily notes - Capture timestamped thoughts
  • Templates - Standardize note creation
  • Tags - Organize and filter content

Why Choose Foam?

  • Free and open source - No subscriptions or vendor lock-in
  • Own your data - Notes stored as standard Markdown files
  • VS Code integration - Leverage powerful editing and extensions
  • Git-based - Version control and collaboration built-in

Foam is like a bathtub: What you get out of it depends on what you put into it.

Getting Started

Coming from Obsidian? See [migrating-from-obsidian].

Features

Recipes

[recipes] is a collection of user-contributed patterns that describe different ways you could utilize Foam or integrate it with other tools.

Publishing

You can publish your Foam notes for consumption in different formats.
Examples: [publish-to-github-pages], [generate-gatsby-site], [publish-to-vercel]

See [publishing] for more details.

Tools


Inbox (`docs/inbox.md`)

Inbox

Uncategorised thoughts, to be added

  • Release notes
  • Markdown Preview
  • Use VS Code CodeTour for onboarding
  • Investigate other similar extensions:
  • Open in Foam
    • When you want to open a Foam published website in your own VS Code, we could have a "Open in Foam" link that opens the link in VS Code via a url binding (if possible), downloads the github repo locally, and opens it as a Foam workspace.
    • Every Foam could have a different theme even in the editor, so you'll see it like they see it
      • UI and layout design of your workspace can become a thing
  • VS Code Notebooks API
  • Future architecture
    • Could we do publish-related settings as a pre-push git hook, e.g. generating footnote labels
    • Running them on GitHub Actions to edit stuff as it comes in
      • Ideally, we shouldn't have to touch files, should be just markdown
  • Looking at the errors/warnings/output panes makes me think, what kind of automated quality tools could we write.
    • Deduplication, finding similarities...
    • Thought Debugger?
    • Knowledge Debugger?
    • Janitor? Gardener?
    • Foam Compiler?
  • Should support Netlify deploys out of the box
  • Foam should tick at the same frequency as your brain, and the Foam graph you build should match the mental model you have in your head, making navigation effortless.
    • Maps have persistent topologies. As the graph grows, you should be able to visualise where an idea belongs. Maybe a literal map? And island? A DeckGL visualization?

Testing: This file is served from the /docs directory.


Principles (`docs/principles.md`)

Principles

Table of Contents

Foam enables you to do your best thinking

  • Foam works for you, you don't work for Foam. You should be able to focus on your work and not fight against Foam, or having to perform fiddly operations or maintenance jobs to keep Foam happy.
  • Foam is not a package deal. You must be able to adopt only the parts of Foam that you want. There must be no tight coupling between Foam's features.
  • Foam is a starting point You must be able to customise how foam looks and feels, and combine it with other tools you find helpful in your personal workflow.
  • Foam is not a philosophy. Whether you use a methodology like Zettelkasten is up to you. You should be able to use Foam without joining a cult.

Foam wants you to own your thoughts

  • Foam doesn't want your data. You can store your documents wherever you want. Some of Foam's suggested workflows include GitHub, if you don't want to use it to store your data, or you want to stop using it in the future, you should be able to migrate to alternative storage options. If you choose to not upload your notes to any cloud service or remote repository, remember to keep frequent local and occasional offsite backups of your data!
  • Foam should not lock you in. Foam's content files, structure and metadata should be in interoperable format that supports migrating it to another tool if you prefer to. Users of Foam should be able to develop and share such tools freely.
  • Foam should not leak your secrets. Nobody, including the developers of Foam, should have access to your personal knowledge graph unless you choose to give it to them. Foams should always be private by default, but easy to share if you choose to.

Foam helps you share your thoughts with the world.

This principle may seem like it contradicts Foam wants you to own your thoughts, but it's actually a compatible corollary. You should be able to do both, because:

[...] environments that build walls around good ideas tend to be less innovative in the long run than more open-ended environments. Good ideas may not want to be free, but they do want to connect, fuse, recombine. โ€”Steven Johnson, Where Good Ideas Come From

  • Foam should make it easy to publish your knowledge graph. With zero code, you should be able to make your graph public to the world. You should have full control over how it looks, feels, and where it's hosted.
  • Foam should make it easy to collaborate on ideas. Foam should allow you to work closely together with your collaborators, and accept feedback, input and improvements from others.
  • Foam should make it easy to publish what you choose. Foam should double as a low-barrier blog/publishing platform, so you can share content to an audience without inviting them to intrude on your entire workspace.

Foam allows people to collaborate in discovering better ways to work, together.

  • Foam is a collection of ideas. Foam was released to the public not to share the few good ideas in it, but to learn many good ideas from others. As you improve your own workflow, share your work on your own Foam blog.
  • Foam is open for contributions. If you use a tool or workflow that you like that fits these principles, please contribute them back to the Foam template as [recipes], [recommended-extensions] or documentation in this workspace. See also: [contribution-guide].
  • Foam is open source. Feel free to fork it, improve it and remix it under the terms of our MIT license.
  • No idea is too big. If it doesn't fit with Foam's core workflow, we might make it a [recipes] page instead.

Foam is for hackers, not only for programmers

While Foam uses tools popular among computer programmers, Foam should be inclusive of everyone who wants to improve their own workflow to improve themselves.

  • Foam embraces the hacker mindset. The target audience for Foam are people who look for creative ways to improve their ability to collect and organise information.
  • Foam is not just for programmers. If you're a programmer, feel free to write scripts and extensions to support your own workflow, and publish them for others to use, but the out of the box Foam experience should not require you to know how to do so. You should, however, be curious and open to adopting new tools that are unfamiliar to you, and evaluate whether they could work for you.
  • Foam is for everyone As a foam user, you support everyone's quest for knowledge and self-improvement, not only your own, or folks' who look like you. All participants in Foam repositories, discussion forums, physical and virtual meeting spaces etc are expected to respect each other as described in our [code-of-conduct]. Foam is not for toxic tech bros.

Dev Code Of Conduct (`docs/dev/code-of-conduct.md`)


redirect_from:

  • /code-of-conduct

Code of Conduct

We follow the Contributor Covenant code of conduct.

Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

  • Demonstrating empathy and kindness toward other people
  • Being respectful of differing opinions, viewpoints, and experiences
  • Giving and gracefully accepting constructive feedback
  • Accepting responsibility and apologizing to those affected by our mistakes,
    and learning from the experience
  • Focusing on what is best not just for us as individuals, but for the
    overall community

Examples of unacceptable behavior include:

  • The use of sexualized language or imagery, and sexual attention or
    advances of any kind
  • Trolling, insulting or derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or email
    address, without their explicit permission
  • Other conduct which could reasonably be considered inappropriate in a
    professional setting

Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at [email protected].

All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

1. Correction

Community Impact: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

Consequence: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

2. Warning

Community Impact: A violation through a single incident or series
of actions.

Consequence: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

3. Temporary Ban

Community Impact: A serious violation of community standards, including
sustained inappropriate behavior.

Consequence: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

4. Permanent Ban

Community Impact: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

Consequence: A permanent ban from any sort of public interaction within
the community.

Attribution

This Code of Conduct is adapted from the Contributor Covenant,
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by Mozilla's code of conduct
enforcement ladder
.

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


Dev Contribution Guide (`docs/dev/contribution-guide.md`)

Contribution Guide

The contribution guide has moved to CONTRIBUTING.md at the repository root.


Dev Devcontainers (`docs/dev/devcontainers.md`)

Using Dev Containers

Foam provides a devcontainer configuration to make it easy to contribute without installing Node and Yarn locally.

Quick start

  1. Install VS Code and the Dev Containers extension.
  2. Open the Foam repository in VS Code.
  3. Run Dev Containers: Reopen in Container from the command palette.

This will build a Docker image with Node 18 and install dependencies using yarn install. Once ready you can run the usual build and test commands from the integrated terminal.


Dev Releasing Foam (`docs/dev/releasing-foam.md`)

Releasing Foam

Foam uses Changesets to manage versions and changelogs across the monorepo (foam-vscode, @foam/cli, @foam/core). Changesets owns the arithmetic โ€” which package gets which bump, and assembling CHANGELOG.md. Deciding what to write is covered by the changelog rules below.

Writing changesets

Fragments are written at release time, reconstructed from the history on main since the last release: see Generating fragments from git history. Changes don't carry them, so a pull request leaves .changeset/ alone.

โš ๏ธ Changes to `@foam/core` must also bump its dependents

foam-vscode and @foam/cli both bundle @foam/core at build time (esbuild inlines it), and both declare it as a devDependency โ€” not a runtime dependency. Because of that, Changesets' automatic internal-dependency bumping (updateInternalDependencies) does not cascade a @foam/core bump to them. It won't add the fragments for you.

So whenever a change touches packages/foam-core, the changeset must also include foam-vscode and @foam/cli (each usually patch), so their republished bundles ship with a correct version and a changelog entry. If you only bump @foam/core, the extension and CLI will silently publish updated code under a stale version number.

Rule of thumb: if @foam/core is in the fragment, foam-vscode and @foam/cli almost always belong there too โ€” unless the core change is genuinely internal-only and reaches neither the extension nor the CLI.

Changelog rules

Does the change need a fragment?

Yes, if it changes behavior that users of a published package can observe: foam-vscode, @foam/cli, @foam/core. (@foam/graph-view, @foam/mcp and @foam/agent-loop are listed under ignore in .changeset/config.json and never get fragments.)

No, for:

  • documentation, CLAUDE.md, .agent/ specs
  • test-only commits โ€” the change they cover carries the fragment
  • refactors with no user-visible effect and no public API change
  • build and tooling changes that don't alter what ships

Which bump?

Foam is pre-1.0, so these are conventions rather than strict semver:

Bump @foam/core foam-vscode / @foam/cli
major reserved โ€” decide deliberately reserved โ€” decide deliberately
minor new or changed public API surface (new export, new subpath, changed signature) new user-facing feature
patch bug fix, security fix, internal change that still ships bug fix, security fix, manifest or user-facing copy fix

When several commits fold into one fragment, use the highest bump any of them warrants.

Writing the entry

The entry is read by users, not by developers. Make it as short as it can be while still telling them what changed: usually one line, rarely more than two sentences.

  • Say what changed, not why it was broken or how it was fixed. Causes, mechanisms and internal names belong in the commit message.
  • If behavior narrowed, say so, and say what to do instead.
  • Name a changed API only in a @foam/core entry, and only name it.
  • Several related fixes can share one entry as a list, one line per item.
  • Reference the issue number when there is one, as (#1215).
  • Don't reuse the commit subject verbatim โ€” commit subjects are written for developers.

For example:

markdown
Notes in folders listed in `files.watcherExclude` are no longer left out of the workspace. To exclude a folder, use `foam.files.exclude`.

not a paragraph on what files.watcherExclude is for, why honouring it saved no CPU, and how the CLI differed.

Generating fragments from git history

At release time, for anything that shipped without a fragment:

  1. List what landed since the last release (tags are vscode@<version>, cli@<version>, core@<version>):

    text
    git log vscode@<last-version>..HEAD --oneline
  2. Group the commits into coherent user-facing changes. Several commits (a fix, its tests, a docs touch-up) usually become one fragment; occasionally one commit becomes two.

  3. For each group apply the rules above: does it need a fragment, which packages, which bump, and what does the user need to know.

  4. Apply the core cascade rule to every fragment that names @foam/core.

  5. Write each one as .changeset/<kebab-case-name>.md:

    markdown
    ---
    'foam-vscode': patch
    ---
    
    The entry text.

The /update-changelog slash command can draft these for commits already on main.

Fragments don't need to be committed before yarn version-packages runs โ€” it reads them off disk. Committing them first is still worthwhile if you want the intent recorded independently of the generated changelog.

Nothing in CI enforces any of this; it's a review-time and release-time responsibility.

Cutting a release

  1. Get to the latest code
    • git checkout main && git fetch && git rebase
  2. Sanity checks
    • yarn reset
    • yarn test
  3. Make sure every shipped change since the last release has a fragment in .changeset/
    • If not, write the missing ones โ€” see Generating fragments from git history
    • If any change touched @foam/core, confirm foam-vscode and @foam/cli are also covered by a fragment (see the warning above). Their bundles pick up core changes at build time, but Changesets will not bump their versions automatically.
  4. Apply the fragments โ€” bumps versions in each affected package.json, regenerates CHANGELOG.md, and deletes the consumed fragments
    • yarn version-packages
  5. Review and commit the result
    • Inspect the generated CHANGELOG.md and version bumps
    • Update ./packages/foam-vscode/WHATS_NEW.md if the release has something users should act on or learn to use. It is shown on update when its content hash changes, so leaving it untouched shows nothing โ€” appropriate for a release with no new features.
    • git add -A && git commit -m "Release"
    • yarn tag-release - Create per-package tags (vscode@<version>, cli@<version>, core@<version>):
      • The script reads each package's current version from package.json and skips any tag that already exists.
  6. Publish
    • yarn release to release them all, otherwise
      • yarn release-extension (packages and publishes to VS Marketplace and OpenVSX)
      • yarn release-cli (packages and publishes @foam/cli to npm as foam-cli)
      • yarn release-core (publishes @foam/core to npm)
  7. Push
    • git push && git push --tags
  8. Update the release notes in GitHub
    • In GitHub, top right, click on "releases"
    • Select "tags" in top left
    • Select the tag that was just released, click "edit" and copy release information from CHANGELOG.md
    • Publish (no need to attach artifacts)
  9. Announce on Discord

Steps 1 to 8 should really be replaced by a GitHub action...


Dev Testing Conventions (`docs/dev/testing-conventions.md`)

Testing in Foam VS Code Extension

This document explains the testing strategy and conventions used in the Foam VS Code extension.

Test File Types

We use two distinct types of test files, each serving different purposes:

`.test.ts` Files - Pure Unit Tests

  • Purpose: Test business logic and algorithms in complete isolation
  • Dependencies: No VS Code APIs dependencies
  • Environment: Vitest with Node.js
  • Speed: Very fast execution
  • Location: Throughout the codebase alongside source files

`.spec.ts` Files - Integration Tests with VS Code APIs

  • Purpose: Test features that integrate with VS Code APIs and user workflows
  • Dependencies: Will likely depend on VS Code APIs (vscode module), otherwise avoid incurring the performance hit
  • Environment: Can run in TWO environments:
    • Mock Environment: Vitest with VS Code API mocks (fast)
    • Real VS Code: Full VS Code extension host (slow but comprehensive)
  • Speed: Depends on environment (see performance section below)
  • Location: Primarily in src/vscode/features/ and service layers

Key Principle: Environment Flexibility for `.spec.ts` Files

.spec.ts files use VS Code APIs, but they can run in different environments:

  • Mock Environment: Uses our VS Code API mocks for speed
  • Real VS Code: Uses actual VS Code extension host for full integration testing

This dual-environment capability allows us to:

  • Run specs quickly during development (mock environment)
  • Verify full integration during CI/CD (real VS Code environment)
  • Gradually migrate specs to mock-compatible implementations

Performance Comparison

Test Type Environment Typical Duration VS Code APIs
.test.ts Vitest (Node.js) fastest No
.spec.ts (mock) Vitest + VS Code Mocks fast Yes (mocked)
.spec.ts (real) VS Code Extension Host sloooooow. Yes (real)

Running Tests

Available Commands

  • yarn test:unit: Runs .test.ts files (no VS Code dependencies) + @unit-ready marked .spec.ts files using mocks
  • yarn test:unit-without-specs: Runs only .test.ts files
  • yarn test:e2e: Runs all .spec.ts files in full VS Code extension host
  • yarn test: Runs both unit and e2e test suites sequentially

Mock Environment Migration

We're gradually enabling .spec.ts files to run in our fast mock environment while maintaining their ability to run in real VS Code.

The `@unit-ready` Annotation

Spec files marked with /* @unit-ready */ can run in both environments:

typescript
/* @unit-ready */
import * as vscode from 'vscode';
// ... test uses VS Code APIs but works with our mocks

Common Migration Fixes

Configuration defaults: Our mocks don't load package.json defaults

typescript
// Before
const format = getFoamVsCodeConfig('openDailyNote.filenameFormat');

// After (defensive)
const format = getFoamVsCodeConfig(
  'openDailyNote.filenameFormat',
  'yyyy-mm-dd'
);

File system operations: Ensure proper async handling

typescript
// Mock file operations are immediate but still async
await vscode.workspace.fs.writeFile(uri, content);

When NOT to Migrate

Some specs should remain real-VS-Code-only:

  • Tests verifying complex VS Code UI interactions
  • Tests requiring real file system watching with timing
  • Tests validating extension packaging or activation
  • Tests that depend on VS Code's complex internal state management

Mock System Capabilities

Our vscode-mock.ts provides comprehensive VS Code API mocking:

Contributing Guidelines

When adding new tests:

  1. Choose the right type:

    • Use .test.ts for pure business logic with no VS Code dependencies
    • Use .spec.ts for anything that needs VS Code APIs
  2. Consider mock compatibility:

    • When writing .spec.ts files, consider if they could run in mock environment
    • Add /* @unit-ready */ if the test works with our mocks
  3. Follow naming conventions:

    • Test files should be co-located with source files when possible
    • Use descriptive test names that explain the expected behavior
  4. Performance awareness:

    • Prefer unit tests for business logic (fastest)
    • Use mock-compatible specs for VS Code integration (fast)
    • Reserve real VS Code specs for complex integration scenarios (comprehensive)

This testing strategy gives us the best of both worlds: fast feedback during development and comprehensive integration verification when needed.

Graph Webview (`@foam/graph-view`)

The webview component has its own test stack, separate from the extension tests.

  • Framework: Vitest with happy-dom for a browser-like environment
  • Location: Test files live in packages/foam-graph/src/
  • Command: yarn workspace @foam/graph-view test

The webview tests focus on the framework-agnostic library code in src/lib/ (graph utilities, color logic, painter). Lit component rendering tests can be added as the component grows.


User Frequently Asked Questions (`docs/user/frequently-asked-questions.md`)

Frequently Asked Questions

  • Ensure that you have all the [recommended-extensions] installed in Visual Studio Code
  • Reload Visual Studio Code by running Cmd + Shift + P (Ctrl + Shift + P for Windows), type "reload" and run the Developer: Reload Window command to for the updated extensions take effect
  • Check the formatting rules for links on [wikilinks]

I don't want Foam enabled for all my workspaces

Any extension you install in Visual Studio Code is enabled by default. Given the philosophy of Foam, it works out of the box without doing any configuration upfront. In case you want to disable Foam for a specific workspace, or disable Foam by default and enable it for specific workspaces, it is advised to follow the best practices as documented by Visual Studio Code

I want to publish the graph view to GitHub pages or Vercel

If you want a different front-end look to your published foam and a way to see your graph view, we'd recommend checking out these templates:

Does Foam collect any data?

Foam collects anonymous usage data (which commands are used, which features are configured) to help prioritize development. No note content, file names, or personal information is ever collected.

Foam follows VS Code's global telemetry setting (telemetry.telemetryLevel). If you have disabled telemetry in VS Code, Foam will not send any data.

To inspect what is being sent, set the Foam log level to Debug (run Foam: Set log level from the command palette) โ€” telemetry events will appear in the Foam output channel. See [foam-logging-in-vscode] for details.

See [telemetry] for the full list of collected data and opt-out instructions.


Dev Design Core Model And Api Review (`docs/dev/design/core-model-and-api-review.md`)

Core Model & API Review โ€” Improvement Spec

  • Date: 2026-08-09
  • Scope: @foam/core information model and services, package/API architecture, and the seams to foam-vscode, @foam/cli, and @foam/mcp. Performance items are included where they fall out of the model review.
  • Method: full read of packages/foam-core/src/model/* and src/services/*, the export subsystem, the consumer packages' integration points, the perf instrumentation (load-profiler, benches, scripts/perf/gate.mjs), and the open issue tracker. Every finding cites file:line against commit 8b03256.
  • Legend: each proposal carries Effort (S โ‰ˆ hours, M โ‰ˆ 1โ€“3 days, L โ‰ˆ 1โ€“2 weeks+), Breaking (against the @foam/core public surface in packages/foam-core/src/index.ts, or user-visible behavior), and Impact with rationale.

Executive summary

The core model is in good shape structurally โ€” the reversed-trie workspace index, the incremental graph, the PublishTarget export abstraction, and the perf-gate CI are all well-designed. The review found no architectural rot, but it did find:

  1. A handful of genuine correctness bugs, including one silent data-loss case in the workspace index (case-colliding filenames), a divergence between the incremental graph and a full rebuild, and rename/edit paths that can corrupt files (no overlap detection, duplicated definition edits).
  2. A semantic gap advertised but not implemented: aliases are offered by link completion but never participate in link resolution, so the extension can suggest a link it will then render as broken.
  3. Lifecycle leaks (tags never disposed, providers never disposed, one failing feature aborts the whole extension activation and strands live resources).
  4. Avoidable hot-path costs that map directly onto the two open perf issues (#1375, #1689): a wasted nested tree-walk in the block parser, an O(nยทm) file matcher, a full SHA-1 + deserialize on every parser-cache hit, and a full-workspace clone per rename.
  5. API-surface debt: a 297-symbol barrel of which ~34% has no in-repo consumer, host concerns embedded in the platform-agnostic core (telemetry endpoint, VS Code-only config getters), and duplicated serializers that make the CLI and MCP emit different wire formats for the same data.
  6. Doc drift: CLAUDE.md describes the pre-foam-core repo layout and actively misdirects contributors (and agents).

The roadmap at the end sequences these into four phases. Phase 1 (correctness + leaks) is entirely non-breaking and roughly two weeks of work; the breaking items are concentrated in Phase 4 and can ride a single minor/major release.


1. Correctness fixes

1.1 Case-colliding filenames silently overwrite each other in the workspace index

FoamWorkspace keys its trie by a reversed, lowercased path (workspace.ts:350-365, normalize at :528). /a/Note.md and /a/note.md produce the identical key, so the second file replaces the first in _resources, and set() emits onDidUpdate instead of onDidAdd (workspace.ts:127-137) โ€” which then desynchronizes the graph (it disconnects the old path while connecting the new one, leaving stale backlinks). The existing #1303 regression test (workspace.test.ts:187-209) uses files in different directories, so this collision is untested. The model currently has two incompatible notions of identity: find(URI) is case-insensitive while URI.isEqual (uri.ts:236-244) is case-sensitive.

Proposal: store exact-case keys in the trie with a case-insensitive lookup layer (bucket resources under the lowercased key), define identity policy in one place, and add the missing same-directory regression test.

Effort: M ยท Breaking: No (fixes a currently-broken case) ยท Impact: High โ€” silent data loss in the model; affects any vault with case-variant filenames (common on case-sensitive filesystems / git checkouts).

1.2 Incremental graph diverges from a rebuild when an added file changes a non-placeholder resolution

onResourceAdded (graph.ts:140-144) only re-resolves sources currently pointing at a placeholder. But adding a file can also change resolution of a link that already resolves to a real note, because ambiguity is broken by path sort (workspace.ts:290) and directory-index suffix match (workspace.ts:229-239). Example: [[attachment-a.pdf]] resolves to /path/to/more/attachment-a.pdf; adding /path/to/attachment-a.pdf should re-point it โ€” the full-rebuild path asserts exactly this (graph.test.ts:183-221), the incremental path misses it. The comment at graph.ts:136-138 acknowledges the missing inverse-resolution capability. graph-incremental.test.ts has no "add steals an already-resolved target" case, and the [flat] perf gate (workspace-graph.bench.ts:132-142) only covers the update path.

Proposal: on add, additionally reconnect sources whose links resolve by identifier to the new resource's basename (the workspace can expose "which identifiers does this URI answer to"); add the missing incremental test and extend the scaling bench to add/delete paths.

Effort: M ยท Breaking: No ยท Impact: High โ€” the graph (backlinks panel, orphans, graph view) is quietly wrong after a common operation until the next full reload.

1.3 Rename/edit pipeline can corrupt files

Three compounding defects:

  • TextEdit.apply (text-edit.ts:18-50) has no overlap detection โ€” overlapping ranges silently corrupt the document.
  • The parser resolves every reference link to the same NoteLinkDefinition object (markdown-parser.ts:204-206), so a file with [a][ref] and [b][ref] makes heading-edit emit two edits with the identical range (heading-edit.ts:85-118, :215-247) โ€” no dedup anywhere, including commands/rename.ts:47-67. Combined with the missing overlap check, the definition line is rewritten twice.
  • createUpdateLinkEdit throws on external links (markdown-link.ts:100-102) and heading-edit.ts:126 calls it outside its try/catch, so one odd link aborts an entire rename.

Proposal: dedupe edits by range before applying; make TextEdit.apply throw on overlaps; guard the analyzeLink/createUpdateLinkEdit call sites consistently. Add the [a][ref] + [b][ref] test.

Effort: S ยท Breaking: No ยท Impact: High โ€” these are the operations users trust most (rename heading/block across the vault); failure mode is silent file corruption.

link-integrity.ts:71 only rewrites wikilink connections; markdown [text](note.md) links are documented as "delegated to VS Code built-in" (link-integrity.test.ts:134). That fallback exists only in the editor โ€” commands/rename.ts:112 and commands/note.ts:306 are the CLI/MCP path, where markdown links silently break on rename.

Proposal: handle link.type === 'link' in link-integrity, optionally behind a linkTypes option so the VS Code host can keep delegating.

Effort: M ยท Breaking: Behavior change for CLI/MCP renames (strictly a fix) ยท Impact: High for CLI/MCP users โ€” renames are currently unsafe outside the editor.

1.5 Tag range extraction has wrong-position bugs

  • Frontmatter tag ranges use substring search (markdown-parser.ts:384-386): tag foo matches inside foobar on an earlier line, producing a wrong range โ€” and TagEdit writes edits at exactly those ranges.
  • Hashtag ranges assume a single-line text node (markdown-parser.ts:399-408): a #tag on the second line of a multi-line paragraph gets a range on the wrong line/column. All existing tests use single-line paragraphs.
  • tag-edit.ts:79-85 distinguishes hashtag vs frontmatter tags by comparing the range length, coupled invisibly to how the parser computes ranges two files away.

Proposal: fix both range computations (word-boundary anchoring; newline-aware offset mapping) with targeted tests, and add a source: 'inline' | 'frontmatter' discriminator to Tag (note.ts:75-78) so tag-edit drops the heuristic (requires a CACHE_VERSION bump in foam-vscode/src/vscode/services/cache.ts:37).

Effort: M ยท Breaking: Tag field addition is additive; cached-resource schema bump only ยท Impact: Medium-High โ€” tag rename writes edits at wrong positions in these cases; also unblocks #1240-adjacent tag work.

1.6 Assorted small correctness fixes (batch)

Fix Where
useAngles off-by-one: target starting with a space isn't angle-wrapped markdown-link.ts:124, note.ts:55 (indexOf(' ') > 0 โ†’ includes(' '))
Missing null check: workspace.find(source) result dereferenced markdown-provider.ts:205
.sort() without comparator on objects โ€” sorts nothing markdown-provider.ts:280
/m vs /gm inconsistency stripping block-anchor markers markdown-provider.ts:55 vs :68
String.replace with string containing user text interprets $ patterns tag-edit.ts:238
Attachment extension case inconsistency: photo.PNG accepted but misclassified attachment-provider.ts:39,65-67,71
Watcher change handler race: two rapid changes to one file, last read wins not last event foam.ts:69-73 โ€” serialize per-URI
Frontmatter tags line lookup can be undefined โ†’ tags silently vanish markdown-parser.ts:376-378

Effort: S (one batch PR) ยท Breaking: No ยท Impact: Medium โ€” each is small, but all are user-visible in edge cases and cheap to fix together.


2. Lifecycle & resource management

2.1 `foam.dispose()` leaks `FoamTags`; workspace never disposes providers

bootstrap's disposer (foam.ts:110-114) disposes subscriptions, workspace, and graph โ€” never tags, whose three workspace listeners (tags.ts:41-45) and emitter leak. FoamTags.dispose() itself also skips its emitter (unlike FoamGraph.dispose(), graph.ts:263-265). Separately, FoamWorkspace.dispose (workspace.ts:460-464) never disposes registered providers even though ResourceProvider extends IDisposable (provider.ts:6).

Proposal: fix both; better, give Foam a DisposableStore so new members can't be forgotten.

Effort: S ยท Breaking: No ยท Impact: Medium โ€” leaks on every reload/deactivate cycle; matters for long-lived hosts and the embeddable MCP library.

2.2 One failing feature aborts extension activation and strands live resources

extension.ts:174 joins all features with Promise.all; a single rejection means context.subscriptions.push(foam, watcher, parserCache, โ€ฆ) at :185 never runs โ€” Foam, the watcher, and the parser cache stay alive and undisposed, and extendMarkdownIt is silently lost (preview loses all Foam rendering). Features are anonymous functions (features/index.ts:15-28), so the error can't even name the culprit.

Proposal: switch to Promise.allSettled with per-feature error logging; push core resources into context.subscriptions immediately after bootstrap; give features an id (and optional supports: 'node' | 'web') โ€” interface FoamFeature { id; supports?; activate(ctx, foam) }. The id also fixes the acknowledged telemetry-key collision risk (types.d.ts:11-12) and gives the web build a declarative way to skip Node-only features (see ยง6.2).

Effort: M ยท Breaking: No (internal to the extension) ยท Impact: High โ€” converts "any feature bug bricks Foam" into "one feature degrades".

2.3 Leaked registrations in graph-webview

graph-webview/index.ts:21 (onDidChangeConfiguration) and :62 (registerCommand('foam-vscode.show-graph', โ€ฆ)) discard their disposables; every other feature pushes them into context.subscriptions.

Effort: S ยท Breaking: No ยท Impact: Low โ€” orphaned registrations on deactivate/reactivate.

2.4 Honor `FoamTags`' `debounceFor` parameter

tags.ts:33 accepts debounceFor but :40 hardcodes debounce(..., 500) โ€” the value is used only as an on/off flag, and bootstrap never passes it, so tags are undebounced in production.

Effort: S ยท Breaking: No ยท Impact: Low alone; pairs with ยง3.4.


3. Performance

These target the two open perf axes tracked by the bench suite (workspace-graph.bench.ts:14-30, #1375) and the slow-load report (#1689, load-profiler.ts:13-14). The perf-gate CI (scripts/perf/gate.mjs: fail at 2.0ร—, warn at 1.3ร—; [linear]โ‰ค1.8, [flat]โ‰ค1.0 normalized) will verify each claim.

3.1 Block parser does a wasted nested tree-walk per node

blocksPlugin.visit (markdown-parser.ts:509-518) calls getDirectText(node) โ€” itself a full subtree visit (:332-340) โ€” for every paragraph/listItem/blockquote/heading, then discards the text whenever there's no ^ block anchor, which is nearly always. This is a constant-factor ~2ร— traversal on flat documents and O(nยทdepth) on nested ones โ€” the single largest avoidable cost in the parse path, which the profiler already identifies as the load bottleneck (load-profiler.ts:218-219, parse is super-linear in note size). Also: note.blocks.some(...) in the same loop is O(blocksยฒ) (:537), and each own-line blockquote anchor re-splits the whole source (:570).

Proposal: gate getDirectText on a cheap "does this node's source range contain ^" check; replace the some with a Set; split the source into lines once per parse and share with the footnote scan (:237).

Effort: S ยท Breaking: No ยท Impact: High โ€” directly attacks #1689/#1375; expected double-digit-% parse-time reduction on typical vaults, verifiable in markdown-parser.bench.ts.

3.2 Parser cache hits still pay SHA-1 + full deserialize

cachedParser (markdown-parser.ts:275-301) hashes the entire document (SHA-1, utils/core.ts:49) on every call, and a hit still runs Resource.fromJSON (foam-vscode/src/vscode/services/cache.ts:201-210). During typing this is the hot path, and the profiler reports it as a cheap "hit".

Proposal: let the host pass a cheaper validity token (document version/mtime) so unchanged documents skip hashing entirely; collapse cache.has + cache.get into one call; consider memoizing the last deserialized resource per URI.

Effort: M ยท Breaking: No (additive cache API) ยท Impact: Medium-High โ€” removes per-keystroke work proportional to note size.

3.3 O(nยทm) file matcher on the VS Code hot path

FileListBasedMatcher.match() filters with Array.includes over a string[] (datastore.ts:168-170); isMatch is O(n) per call. This is the matcher VS Code selects in the common case (editor.ts:495-502) โ€” on a 10k-file vault a single match(allFiles) is ~10โธ comparisons. The class carries its own "to be refactored later" note (:150).

Proposal: back it with a Set<string>. While in there: its include/exclude fields are decorative (never consulted) โ€” either wire or remove them. This is also the natural place to add .gitignore support (#1388), the most-requested open datastore issue.

Effort: S (Set) + M (gitignore) ยท Breaking: No ยท Impact: Medium-High โ€” removes a quadratic on load and on every matcher refresh; #1388 has standing user demand.

3.4 `FoamTags` rebuilds the entire tag index on every file event

tags.update() (tags.ts:51-61) is a full O(total tags) rebuild wired to all three workspace events (:42-46), unlike the graph which is incremental. Every panel listening to tags.onDidUpdate then refreshes wholesale.

Proposal: incremental update using the {old, new} payload the workspace already provides (index tag locations by resource as well as label).

Effort: M ยท Breaking: No ยท Impact: Medium โ€” flattens per-edit cost in tag-heavy vaults; pairs with ยง2.4 and ยง4.6.

buildFutureWorkspace (link-integrity.ts:13-26) copies every resource into a fresh FoamWorkspace to answer one identifier question โ€” a full second index per rename on large vaults.

Proposal: an overlay resolver over the live workspace (consult the overlay first, fall through).

Effort: M ยท Breaking: No ยท Impact: Medium โ€” rename latency on large vaults.

3.6 Other measured/mechanical wins (batch)

  • _unregisterDirectoryIndex is O(workspace) on every delete of an index file (workspace.ts:185-212) โ†’ keep per-directory candidate lists.
  • TextEdit.apply builds a per-character array per edit (text-edit.ts:22-50) โ†’ offset-based slice concatenation.
  • Per-node plugin dispatch re-enters a try/catch and probes 6 optional methods per AST node (markdown-parser.ts:183-189) โ†’ precompute the visitor list per parser.
  • getTagAtPosition scans all tags ร— locations per call (tag-edit.ts:265-283) โ†’ per-URI index.
  • CLI gets neither parser cache nor profiler (foam-cli/src/support/filesystem.ts:172) โ†’ wire both; big cold-start win for CLI/MCP and export.
  • Profiler hygiene: no reset() (double-counts on second load), chars misreported as MiB (load-profiler.ts:63-67, :147, :237).

Effort: S-M per item ยท Breaking: No ยท Impact: Medium in aggregate.

3.7 (Strategic) Replace remark-parse v8 / unified v9 with micromark

The pinned parser stack is several majors behind. It is the root cause of: the regex-based footnote scan (markdown-parser.ts:231-268), the collapsed consecutive-footnote ASTs, likely the documented super-linear parse scaling, and the wikilink-in-math false positives (#1317). The bench file already ships an A/B harness for exactly this migration (markdown-parser.bench.ts:97-101).

Proposal: migrate to micromark/mdast-util-from-markdown behind the existing ParserPlugin seam; prove parity with the golden-file tests and the A/B bench before switching.

Effort: L ยท Breaking: Potentially (AST-shape-dependent plugins; parse edge cases change) ยท Impact: High long-term โ€” removes a whole class of workarounds and the main super-linear cost; should be scheduled, not rushed.


4. Information model evolution

Aliases are parsed (markdown-parser.ts:700-712), stored on Resource, offered by link completion (link-completion.ts:269) and search โ€” but listByIdentifier (workspace.ts:266-291) only ever consults paths. [[my-alias]] yields a placeholder even when a note declares alias: my-alias: the system suggests links it then fails to resolve. This is the single biggest semantic gap in the model.

Proposal: index aliases alongside basenames with documented precedence (exact path > basename > alias), behind a config flag initially (matching the directoryMode precedent, markdown-provider.ts:24).

Effort: M ยท Breaking: Behaviorally (previously-broken links start resolving โ€” flag-gated) ยท Impact: High โ€” closes a visible feature contradiction; frequently expected by users coming from Obsidian.

4.2 Normalize placeholder identity in the model

URI.placeholder(path) does no normalization (uri.ts:99-101): [[page-b]] and [../page-b.md] denoting the same missing file become two distinct graph nodes (pinned in graph.test.ts:225-260), so the placeholders panel shows duplicates. Placeholder construction is scattered across four sites in markdown-provider.ts (:120,132,152,167), and "placeholder-ness" is modeled three different ways (URI scheme uri.ts:201-203; a phantom Resource.type === 'placeholder' that the workspace never stores; a template-trigger type).

Proposal: a single model-owned placeholder factory that canonicalizes identity (workspace-resolved path where resolvable, raw text kept as label); remove the phantom resource type or promote it to a real variant.

Effort: M ยท Breaking: Yes โ€” placeholder paths are visible in graph.placeholders, the panel, and tests ยท Impact: Medium-High โ€” correct placeholder dedup, simpler consumers, prerequisite for sane "create note from placeholder" flows.

4.3 Split `FoamWorkspace.find` and fix its inconsistencies

find(URI | string) (workspace.ts:367-415) is three functions behind one signature (trie get / identifier resolution / path probing across roots), and it returns the stored object normally but a fresh shallow copy when the reference carries a #fragment (:408-413) โ€” reference equality depends on argument syntax. Ambiguity resolution ("alphabetically first path wins") is encoded in a sort call (:290).

Proposal: explicit findByUri / findByIdentifier / findByPath; fragment handling returns {resource, section} instead of a patched copy; document the ambiguity policy. Keep find as a deprecated shim for one release.

Effort: M ยท Breaking: Yes (widely used in foam-vscode; mechanical migration) ยท Impact: Medium โ€” the API's biggest ambiguity source; enables ยง1.2's identifier inversion cleanly.

4.4 Key the graph by full URI identity, not `uri.path`

links/backlinks/placeholders are Map (graph.ts:21-29, 247-258): in a virtual workspace file:///notes/a.md and vscode-vfs://github/notes/a.md collide (multi-scheme resolution is explicitly supported โ€” workspace.test.ts:303-326); fragments are silently ignored, so callers must remember .asPlain() (done in five places in commands/links.ts). The same path-keyed pattern recurs in the export context (export/types.ts:179,184) and embeddings (ai/model/embeddings.ts:39).

Proposal: introduce a small URIMap<T> (canonical scheme://authority/path keying, explicit fragment policy) in model/uri.ts and use it in graph, export, embeddings. Make getLinks/getBacklinks normalize their argument.

Effort: M ยท Breaking: Yes for direct readers of graph.links/graph.backlinks (public mutable maps โ€” see ยง4.5) ยท Impact: Medium โ€” correctness on virtual/multi-root workspaces (web extension, remote repos), removes a caller ritual.

4.5 Encapsulate the graph/tags collections

graph.placeholders/links/backlinks and tags.tags are public readonly Maps โ€” the reference is readonly, the contents are not; any consumer can foam.graph.links.clear(). Stored Resources are also held by reference with no copy (workspace.ts:131), so in-place mutation corrupts the index and defeats the {old, new} update diff.

Proposal: accessor methods returning ReadonlyMap/iterators; document (or enforce via Object.freeze in dev builds) that Resource is immutable once stored.

Effort: S-M ยท Breaking: Yes, mechanical ยท Impact: Medium โ€” removes a foot-gun class; prerequisite for safely evolving internals (ยง4.4).

4.6 Batchable events with payloads

workspace.clear() fires one delete per resource (workspace.ts:149-158) โ†’ N graph mutations + N full tag rebuilds + N panel refreshes; there is no batch/transaction primitive even though PauseableEmitter/EventBufferer already exist (common/event.ts:749, 878). graph.onDidUpdate and tags.onDidUpdate are Event<void>, so all ~7 VS Code listeners can only blanket-refresh. Delivery is synchronous and re-entrant (common/event.ts:706-733).

Proposal: additive first โ€” onDidChangeBatch: Event<Change[]> on the workspace, used by clear() and bulk load; a debounced aggregate Foam.onDidChangeWorkspace (the preview feature currently hand-rolls this from three subscriptions, preview/index.ts:20-30). Later, upgrade onDidUpdate to carry a delta (breaking for listeners).

Effort: M (additive) + S (listener migration) ยท Breaking: Additive now; delta payload later is breaking ยท Impact: Medium-High โ€” turns O(N) panel refresh storms into O(1); foundation for scaling to large vaults.

4.7 Type-model tightening

  • Resource.type: string โ†’ union 'note' | 'attachment' | 'image' (note.ts:146); properties: any โ†’ Record<string, unknown> (:147). Breaking, mostly mechanical.
  • ResourceLink.definition?: string | NoteLinkDefinition encodes a three-state machine decoded by four predicates with order-sensitive call sites (note.ts:5-44; markdown-provider.ts:94-138) โ†’ discriminated union {kind: 'inline'} | {kind: 'ref-unresolved'; label} | {kind: 'ref-resolved'; definition}. Breaking (parser + tests construct it).
  • Connection (graph.ts:8-12) carries no resolution provenance โ†’ add resolvedBy: 'path' | 'identifier' | 'directory-index' | 'placeholder'; every consumer currently re-derives this. Additive. Also directly serves typed-links exploration (#1626).
  • URI derivations (getDirectory, joinPath, โ€ฆ) preserve fragment/query (uri.ts:125-152), yielding directories with section anchors; asPlain() exists solely to undo it โ†’ drop fragment/query on parent/derivation ops. Non-breaking in practice (all call sites strip or have none).
  • asAbsoluteUri (uri.ts:465-493) disagrees with FoamWorkspace.resolveUri on the primary case (uri.test.ts:131-140 vs workspace.test.ts:277-281) and is publicly exported โ†’ redefine in terms of resolveUri or remove. Breaking.
  • Dead indirection: pathToPlaceholderId is the identity function (graph.ts:14); wikilinkPlugin.onDidVisitTree is an empty function (markdown-parser.ts:798-801); duplicate CancellationToken in progress.ts:21-24. Delete. Non-breaking.

Effort: S-M per item ยท Impact: Medium โ€” compile-time safety over runtime predicates; cheapest during the same release that takes ยง4.3/ยง4.4.

4.8 Parser instance isolation

sectionStack is module-level mutable state shared by every parser instance (markdown-parser.ts:416), and onDidInitializeParser mutates the module-level unified processor (:63, :105) so extraPlugins from one createMarkdownParser call leak into all others. Safe today only because parsing is synchronous and single-instance.

Proposal: per-instance processor; per-parse context object threaded through ParserPlugin (breaking for the plugin interface, which has few implementors).

Effort: M ยท Breaking: Yes (plugin interface) ยท Impact: Medium โ€” prerequisite for embedded-note parsing, parallel parse, and the ยง3.7 migration.


5. API & package architecture

5.1 Tier and split the public API surface

@foam/core exposes a single 315-line barrel re-exporting 297 symbols; cross-referencing all three in-repo consumers, ~102 are never imported by anyone. Output is CJS-only, so importing URI drags in jexl, the export pipeline, and lint rules โ€” the web bundle and MCP pay for subsystems they never call.

Proposal: add subpath exports (@foam/core/model, /export, /query, /lint) and ESM output alongside CJS; split the barrel into a documented contract tier and an internal tier; add an API-report CI check (api-extractor or attw). Root barrel should delegate to per-subsystem index.ts curators โ€” today src/index.ts:194-239 hand-duplicates export/index.ts's list and the two have already diverged.

Effort: M-L ยท Breaking: Additive now; demoting unused symbols is breaking for hypothetical external users only ยท Impact: High โ€” bundle size for web/MCP, and it establishes the semver contract before external consumers depend on accidental surface.

5.2 Move host concerns out of core

  • The App Insights connection string and envelope builder live in the platform-agnostic core (telemetry.ts:158-159, exported at index.ts:180-184). The string is deliberately a non-secret ingestion key, but a product's telemetry destination is deployment config, not library code โ€” any fork/embedder silently inherits Foam's endpoint.
  • IFoamConfig (config.ts:1-46) carries โ‰ฅ8 editor-UI getters (getGraphOnStartup, getPreviewEmbedNoteType, getCompletionLabel, โ€ฆ) that a headless CLI is forced to stub (foam-cli/src/support/config.ts:60-65).
  • Config is a mutable process-global singleton (config.ts:149-166) with exactly one core reader (template-discovery.ts:15) โ€” a real hazard for @foam/mcp, which is meant to be embeddable (two workspaces in one process can't differ).

Proposal: core keeps ITelemetryReporter + bucketing only; endpoint + consent copy move to hosts. Split CoreConfig (files, templates, daily notes) from host config. Thread config explicitly (getTemplatesDir(rootUri, config)), retire the global.

Effort: M ยท Breaking: Yes โ€” small, in-repo call-site count (โ‰ค5) ยท Impact: Medium-High โ€” makes core genuinely embeddable and forkable; unblocks publishing @foam/mcp.

5.3 Retire the `foam-vscode` shim layer; repatriate stranded platform-agnostic code

Four directories in foam-vscode/src have zero vscode imports: src/core/ (a second bootstrap whose 8th positional parameter diverges from core's โ€” foam-vscode/src/core/model/foam.ts:23-32 vs foam.ts:32-42, an accident waiting to happen), src/ai/model + src/ai/services (382-line FoamEmbeddings, pure core), src/lint/, and src/daily-note/. The shim type also makes the feature registry contravariantly unsound โ€” it only compiles because strict: false.

The lint case is user-visible: the four wikilink rules (rule-check-links.ts:22) are only reachable by the editor, so foam lint in CI cannot catch broken wikilinks while the editor can (foam-cli/src/commands/lint.ts:14-18). Relatedly, lintNote hardcodes its rule list while lintWorkspace takes rules[] (lint.ts:47-49, 86) โ€” two extensibility models in one file.

Proposal: move embeddings/lint/daily-note logic into core (embeddings as an optional Foam member; Ollama provider stays host-side); make core's bootstrap take an options object (it has nine positional parameters); delete the shim. Align lintNote/lintWorkspace on injected rules + defaultLintRules().

Effort: M-L ยท Breaking: bootstrap and lintNote signatures (3 and 2 call sites); rest is internal moves ยท Impact: High โ€” one bootstrap, CLI lint parity (CI catches broken wikilinks), and unlocks strict: true for the extension.

5.4 Unify the CLI/MCP wire format

foam-cli/src/support/serializers.ts (148 lines) and foam-mcp/src/serializers.ts (203 lines) independently serialize the same nine core result types โ€” and disagree: CLI emits absolute fs paths (serializers.ts:45), MCP emits workspace-relative POSIX paths with placeholder: handling (serializers.ts:52-55). Same note, two encodings; CLI has no placeholder story at all.

Proposal: one @foam/core/serialize module owning the Json* types and URI encoding (option: 'fs' | 'workspace-relative'); both hosts consume it. Prefer unifying on workspace-relative.

Effort: M ยท Breaking: CLI JSON output changes if unified (recommended; flag the old encoding) ยท Impact: Medium โ€” consistency for scripts/agents consuming both surfaces; halves maintenance.

5.5 Honor the `IDataStore` contract; make capabilities explicit

The interface requires list(pattern) to filter (datastore.ts:12-17) but the VS Code implementation ignores the pattern entirely (editor.ts:408 takes no parameter) โ€” currently masked only because saved queries build their own store (saved-store.ts:102-138); any future dataStore.list(glob) call in the extension is a silent data bug. GenericDataStore also throws at runtime for the 4-of-6 optional operations while claiming the full interface (datastore.ts:117-143), and read conflates missing vs unreadable (both null, :106-115). None of the shipping implementations have direct tests.

Proposal: fix the VS Code listFiles to honor the glob; split IReadableDataStore/IWritableDataStore (or a capabilities probe); distinguish not-found from error; add the missing tests.

Effort: M ยท Breaking: Interface split is breaking (4 implementations, all in-repo) ยท Impact: Medium โ€” closes a documented contract violation before it becomes a bug.

5.6 Packaging hygiene (batch)

  • @foam/core/test relative-imports ../src/*, forcing a dual-instance-of-URI alias hack in three vitest configs (foam-vscode/vitest.config.mts:41-48 documents it), while instanceof URI is used in production (convert-links-format.ts:26); test-utils also imports micromatch, which is not a declared dependency, and there's no files allowlist so npm ships src/, test-data/, etc. โ†’ import via the package entry, fix deps, add files.
  • foam-vscode duplicates 15 of core's dependencies as phantom runtime dependencies (nothing installs them; esbuild bundles) โ€” version skew between the two package.jsons would silently ship a different parser than tests run โ†’ move to devDependencies, single source of truth in core.
  • @foam/mcp declares "@foam/core": "*" as a runtime dep, but core publishes under the name foam-core (release.js:29-31) โ€” the moment @foam/mcp is published it cannot resolve โ†’ bundle like cli/vscode, or publish under the real name.
  • Dead cli build target in foam-vscode/esbuild.js:31,136-146,177-188 (the src/cli/ it builds no longer exists) โ†’ delete.
  • Three field-identical graph-data types (graph-data-builder.ts:5-16, export/types.ts:126-144, foam-graph/src/protocol.ts:16-25), with build-site-graph.ts:12 compiling only by structural coincidence โ†’ alias the two in-core copies; keep the protocol copy as a deliberate wire contract with a compile-time compatibility assertion.
  • graph-data-builder.ts:75 ships the entire raw frontmatter into graph payloads consumed by the webview and the published static site โ€” private frontmatter fields leak into published graph JSON โ†’ allowlist exported keys.

Effort: S-M per item ยท Breaking: No (except the frontmatter allowlist, a behavior fix) ยท Impact: Medium โ€” the frontmatter leak in particular is a privacy fix for publishing.


6. Platform integrity (web / CLI parity)

6.1 Mechanically enforce "no Node imports in foam-core"

The invariant holds today by review only: core's tsconfig includes "types": ["node"], so process/Buffer type-check fine. One lint rule (no-restricted-imports on fs|path|os|node:* in packages/foam-core/src) makes the repo's most important invariant enforceable.

Effort: S ยท Breaking: No ยท Impact: High leverage for the cost โ€” prevents an entire regression class.

6.2 `export-html-page` ships broken in the web extension

It imports fs/promises directly (export-html-page/index.ts:2,85,91) and is registered unconditionally; the web build's polyfillNode() makes it bundle but not work. Two other fs-users get bespoke esbuild onResolve swaps (esbuild.js:72-101) โ€” a substring-matching mechanism invisible from the source tree.

Proposal: route file I/O through foam.services.dataStore (already works in both hosts); replace the regex swap mechanism with explicit .web.ts twins or browser field mapping; use ยง2.2's supports field to gate genuinely Node-only features.

Effort: S (gate) + M (dataStore refactor) ยท Breaking: No ยท Impact: Medium โ€” a visibly broken command in the web host today.

6.3 Path-string discipline cleanup

Per the repo's own URI convention: grouped-resources-tree-data-provider.ts:1,85-89 runs Node path.parse on a URI component; export/types.ts:72 accepts contentRoot?: string | URI (the one both-typed field in core's config surface โ€” a vscode-vfs:// workspace will resolve incorrectly); foam-cli/src/support/filesystem.ts:28-34 reimplements core's isWithinPath under the same name with a different signature. Also export getBasename/getExtension/getDirectory from core's barrel โ€” CLAUDE.md tells contributors to use them, but they aren't currently exported (index.ts:151-158), which is part of why reimplementations happen.

Effort: S ยท Breaking: contentRoot: URI only (2 in-repo call sites) ยท Impact: Low-Medium each; collectively keeps the convention honest.


7. Documentation

7.1 `CLAUDE.md` describes the pre-`foam-core` repository and actively misdirects

It states core lives in packages/foam-vscode/src/core/ (now a one-file shim), features in src/features/ (now src/vscode/features/), claims a tsconfig paths mapping for @foam/graph-view that doesn't exist, and recommends path utilities core doesn't export. CONTRIBUTING.md:58-62 is correct, so the two root docs contradict each other. Also: docs/user/features/templates.md:284 links to a moved file (user-facing broken link), and docs/dev/testing-conventions.md:25 is stale.

Proposal: rewrite the structural sections of CLAUDE.md against the current tree and make it point to CONTRIBUTING.md for structure (the duplication is what drifted); fix the two stale doc links.

Effort: S ยท Breaking: No ยท Impact: High for contributor/agent effectiveness โ€” this file is loaded into every AI-assisted session and currently sends it to the wrong directories.


Prioritized roadmap

Phase 1 โ€” Correctness & leaks (all non-breaking; ~2 weeks)
1.1 trie case collision ยท 1.2 incremental-add divergence ยท 1.3 edit corruption ยท 1.5 tag ranges ยท 1.6 small-fix batch ยท 2.1โ€“2.4 lifecycle fixes ยท 5.6 frontmatter leak in published graphs ยท 6.1 core import lint ยท 7.1 CLAUDE.md rewrite

Phase 2 โ€” Performance (non-breaking; ~1โ€“2 weeks; verified by the existing perf gate)
3.1 block-parser walk ยท 3.3 matcher Set (+#1388 gitignore) ยท 3.2 cache-hit cost ยท 3.6 mechanical batch ยท 3.4 incremental tags ยท 3.5 rename overlay

Phase 3 โ€” Parity & packaging (mostly non-breaking; ~2 weeks)
5.3 lint rules to core (CLI wikilink lint) ยท 1.4 CLI markdown-link rename ยท 6.2 web export fix + feature supports ยท 5.4 shared serializers ยท 5.6 packaging batch ยท 4.6 batch events (additive part) ยท 2.2 feature isolation + ids

Phase 4 โ€” Model & API evolution (breaking; one coordinated minor/major)
4.1 alias resolution (flagged) ยท 4.2 placeholder identity ยท 4.3 find split ยท 4.4 URIMap graph keying ยท 4.5 collection encapsulation ยท 4.7 type tightening ยท 4.8 parser isolation ยท 5.1 API tiering + subpaths ยท 5.2 config/telemetry out of core ยท 5.5 datastore contract ยท then 3.7 micromark migration behind the A/B bench

Issue cross-references: #1375/#1689 (Phases 2, 4-parser), #1388 (3.3), #1240 (1.5/4.7 groundwork), #1317 + #1631 (3.7), #1626 (Connection provenance, 4.7), #1685 (safe to do alongside 3.2), #1303 (1.1 closes the remaining gap).


Dev Design Improved Static Site Generation (`docs/dev/design/improved-static-site-generation.md`)

Improved Static Site Generation

Summary

Foam should adopt a two-layer publishing architecture:

  • a Foam-owned publishing layer in TypeScript that understands Foam semantics
  • a modern static site shell, with Astro and Starlight as the default implementation

This keeps Foam aligned with the project's build-vs-assemble principle while still giving users a polished website for browsing their knowledge base.

Problem

Today, Foam has publishing documentation and community templates, but not a single modern architecture that:

  • preserves Foam semantics reliably
  • produces a high-quality static site
  • gives the project a durable TypeScript integration point
  • remains optional instead of locking Foam to one publishing target

If Foam only chooses a website framework, the framework ends up becoming the architecture. That is too brittle. Foam needs a publishing pipeline of its own.

Goals

  • Publish a Foam knowledge base as a static website
  • Preserve Foam-specific semantics where possible:
    • wikilinks
    • aliases
    • block anchors and section links
    • embeds
    • tags
    • backlinks
    • graph metadata
    • publish visibility rules
    • foam-query blocks, at least where they can be resolved safely at build time
  • Reuse Foam parsing and graph logic
  • Keep the publishing path framework-agnostic enough to support other site targets later
  • Make the default user experience modern and visually strong

Non-Goals

  • Building a bespoke all-in-one site generator as the first step
  • Replacing ordinary Markdown authoring with a new required syntax
  • Requiring VS Code APIs during website generation
  • Solving every interactive publishing feature in the first iteration

Default Site Stack

  • Framework: Astro
  • Site shell: Starlight
  • Content input: generated Markdown plus derived JSON metadata
  • Optional richer authored pages: Markdoc or MDX where needed

Why This Stack

Astro and Starlight provide the polished site quickly. Foam's own publishing layer protects the project from tying note semantics directly to any one site framework.

This means Foam can:

  • ship a good default website
  • keep the core publishing logic in TypeScript
  • preserve the option to support other renderers later

Proposed Architecture

1. Foam Publishing Layer

Add a publishing layer responsible for turning a Foam workspace into publishable artifacts.

The logical module should not depend on VS Code. It should consume Foam core models and services.

For implementation, this should be phased:

  • first iteration: packages/foam-vscode/src/publish
  • later extraction: packages/foam-publish

The first iteration should be written as if it were already an extractable package. That means keeping it framework-agnostic, avoiding vscode imports, and giving it a clean entrypoint and module structure.

Suggested responsibilities:

  • discover and load publishable notes from a workspace
  • resolve wikilinks, aliases, and anchors using Foam's own workspace and graph logic
  • evaluate publish visibility rules
  • rewrite links for the target site
  • expand or rewrite embeds
  • compute backlinks
  • compute graph/search metadata
  • render or precompute foam-query output when possible
  • emit a normalized set of output files for a site framework

Suggested inputs:

  • workspace root
  • publish target configuration
  • output mode and path

Suggested outputs:

  • transformed Markdown files for each published note
  • JSON indexes for search, backlinks, graph, and route metadata
  • copied static assets and attachments

2. Starlight Target

Add a Starlight target inside the Foam publishing layer that materializes a runnable site directory.

Responsibilities:

  • render the transformed notes into a Starlight-compatible content tree
  • write the site scaffold needed to run Astro and Starlight in the output directory
  • provide navigation, layout, SEO, and search UI through the generated Starlight app
  • keep Foam-specific semantics in the publish layer rather than in the site framework

This target should stay thin. It is an implementation of the publishing output, not the source of truth for Foam semantics.

3. CLI and Automation

Expose the pipeline through a CLI entry point, likely via Foam CLI or a dedicated command.

Examples:

  • build a local static site
  • watch and rebuild during development
  • emit publishable artifacts for GitHub Pages, Vercel, or Netlify

This matches the existing direction in the repository toward build-time transformations and automation.

Data Flow

The first version should follow this pipeline:

  1. Read the Foam workspace using core services
  2. Filter notes according to publish rules
  3. Parse notes and resolve Foam constructs
  4. Emit transformed Markdown for the site
  5. Emit derived JSON indexes
  6. Let the generated Astro and Starlight site build the final static HTML

In shorthand:

Foam workspace -> foam-publish -> Markdown + JSON -> Astro/Starlight -> static HTML

Why Generated Markdown Plus JSON

This is the most practical first output format.

Advantages:

  • It keeps the content inspectable and portable
  • It works naturally with Starlight and Astro content collections
  • It avoids forcing the site layer to reimplement Foam parsing logic
  • It gives the site enough structured metadata for backlinks, graph views, and search-related UI

Generating only HTML would make customization harder. Generating only a raw graph JSON model would push too much work into the site layer. Markdown plus derived JSON is the right balance.

Treatment of Foam Features

Resolve them at build time to stable site routes. The site should not need to guess how Foam resolution works.

Emit stable heading and block IDs during transformation so links and popovers can target exact content.

Embeds

Handle note embeds at build time where possible. Preserve attachment embeds and image references as normal site assets.

Compute backlinks in foam-publish and expose them as page metadata or a site-wide index.

Graph View

Emit graph data as JSON derived from Foam's own graph model. Render it in the site layer with a dedicated component.

`foam-query`

Treat query blocks as build-time features when safe and deterministic. Where execution is unsafe or unsupported, fail clearly and leave a readable placeholder in output.

Publish Visibility Rules

Implement this in foam-publish, not in the site layer. The site should only receive already-filtered content.

Why Not Make Markdoc the Core

Markdoc is a good integration option, but not a good center of gravity for the first version.

Reasons:

  • Foam notes should stay ordinary Markdown first
  • Foam already has its own semantics to preserve
  • switching the base representation to Markdoc would increase coupling and reduce portability

Markdoc is still useful for richer authored documentation pages outside the imported note corpus.

Why Not Start With Quartz

Quartz is close enough to Foam that it is worth studying and possibly spiking against. However, starting directly from Quartz would blur the boundary between Foam semantics and site implementation.

The project should instead:

  • borrow feature ideas from Quartz
  • compare experience against Quartz during prototyping
  • keep Foam's publishing logic separate from the chosen renderer

Incremental Delivery Plan

Phase 0: Internal Publish Spike

  • Implement the publishing layer in packages/foam-vscode/src/publish
  • Reuse existing Foam core and parser code where possible
  • Keep the module free of vscode imports
  • Prove the basic API and output shape before extracting a standalone package

Phase 1: Minimal Viable Site

  • Publish filtered notes as routed pages
  • Rewrite wikilinks and attachments
  • Generate navigation from folders and titles
  • Support search through the default Starlight capabilities or a simple generated index

Phase 2: Foam Semantics

  • Add backlinks
  • Add block anchors and section-deep links
  • Add note embeds
  • Add publish rules

Phase 3: Knowledge-Base Experience

  • Add graph view
  • Add hover previews
  • Add richer query rendering
  • Add deployment presets and starter templates

Open Questions

  • What exact publish-rule syntax should Foam support: public-by-default, private-by-default, or folder-based publishing?
  • Should foam-query-js be supported in published output at all, or only the declarative query language?
  • Should the first version generate files directly into a site workspace, or into an intermediate output folder consumed by the site package?
  • How much of the existing preview pipeline can be shared directly, and how much should move into framework-agnostic publish utilities?

Proposed Decision

Adopt Astro plus Starlight as the default website stack, and implement a framework-agnostic Foam publishing layer that emits transformed Markdown and derived JSON for the site to render.

Start that implementation in packages/foam-vscode/src/publish, then extract it to packages/foam-publish once the API and boundaries have stabilized.

That gives Foam the right default without collapsing publishing strategy into a single website framework.


Dev Design Static Site Publishing Research (`docs/dev/design/static-site-publishing-research.md`)

Static Site Publishing Research

Researched on April 17, 2026.

Context

Foam already has a publishing story, but it is spread across older templates and partial proposals rather than a single modern architecture.

Relevant existing material in this repository:

  • Publishing pages says Foam should remain valid Markdown and be publishable by many tools, while leaving room for a more Foam-aware publishing path later.
  • Improved static-site generation, search in published workspaces, graph in published workspaces, and publishing permissions are all established goals.

That gives a fairly clear constraint set:

  • Published output should preserve Foam semantics such as wikilinks, aliases, embeds, block anchors, backlinks, and queries where practical.
  • The publishing path should stay optional. A Foam workspace should still be usable as normal Markdown without requiring a bespoke site generator.
  • The implementation should fit a TypeScript-heavy codebase and reuse Foam's parsing and graph logic instead of reimplementing it in another stack.

Evaluation Criteria

The tools were evaluated against these criteria:

  • Quality of the generated site for documentation and knowledge-base content
  • Ease of integrating Foam-specific behavior
  • TypeScript support and extensibility
  • Static output and deployment simplicity
  • Suitability for a navigable knowledge base rather than only linear product docs

What Obsidian Publish Gets Right

Obsidian Publish is the product benchmark, not the implementation benchmark.

Its public positioning emphasizes the reader experience for connected notes: hover previews, graph view, stacked navigation, backlinks, strong SEO, mobile support, and easy customization. Those are the right north-star features for Foam publishing even if Foam should not copy the hosted-product model.

Source:

Option Review

Astro

Astro is a strong foundation layer for Foam publishing.

Why it fits:

  • It is modern, fast, and static-first.
  • It has first-class TypeScript support.
  • It supports Markdown and MDX directly.
  • It has clear extension points through remark and rehype plugins.
  • It has typed content collections that work well for structured frontmatter and derived metadata.

Most importantly, Astro is a good host for a Foam publishing adapter. It does not force Foam to adopt a new authoring model just to get a good site.

Sources:

Starlight

Starlight is the strongest out-of-the-box shell among the options reviewed.

Why it stands out:

  • It produces polished documentation sites quickly.
  • It includes site navigation, search, SEO, internationalization, readable typography, code highlighting, and dark mode out of the box.
  • It supports Markdown, Markdoc, and MDX.
  • It adds frontmatter validation with TypeScript type safety.
  • It keeps access to the wider Astro ecosystem instead of boxing the project into a narrow template.

For Foam, this means the team can spend effort on Foam behavior instead of rebuilding the surrounding documentation-site basics.

Source:

Markdoc

Markdoc is powerful, but it should not be the center of Foam's publishing architecture.

It is a good option for adding structured, component-rich authoring to specific parts of a site. However, using Markdoc as the canonical representation of Foam notes would move Foam away from portable Markdown and toward a richer custom syntax. That tradeoff does not look justified for the first version.

Markdoc is best treated as an optional layer for advanced authored pages, not as the base representation of the knowledge base itself.

Sources:

Quartz

Quartz is the closest match to Foam's use case and should be treated as the main reference implementation.

Why it matters:

  • It is explicitly oriented toward note graphs and digital gardens.
  • It is configured in TypeScript.
  • It exposes a plugin pipeline with transformers, filters, and emitters.
  • It already supports popover previews, backlinks, graph view, explorer navigation, and full-text search.
  • It includes an ObsidianFlavoredMarkdown plugin, which shows direct alignment with the knowledge-base space.

Quartz is compelling enough that it could be used for a fast proof of concept. The reason not to pick it as the primary foundation is not quality; it is control over architecture. Foam still needs a publishing layer for features like foam-query, publish permissions, and reuse of Foam core models. Once that layer exists, Astro and Starlight provide a cleaner long-term substrate for a broader website experience.

Sources:

Docusaurus

Docusaurus is a capable modern documentation platform, but it does not look like the best fit for Foam's first publishing path.

It has strong support for search, versioning, internationalization, MDX, and theme customization. The issue is fit rather than capability: it is more naturally shaped for product documentation than for a wiki-like knowledge base with Foam-specific semantics.

It remains a viable option for projects that want a more React-centric docs platform, but it is not the best default recommendation for Foam.

Source:

Recommendation

The recommended direction is:

  1. Use Astro as the web framework.
  2. Use Starlight as the website shell.
  3. Build a Foam-specific publishing layer in TypeScript.
  4. Keep Markdoc optional.
  5. Use Quartz as a reference point and comparison target, especially for graph, popover, backlinks, and navigation behavior.

This direction gives Foam a modern, polished static site without forcing Foam to become a site generator.

Main Architectural Takeaway

The core design problem is not choosing a renderer. The core design problem is preserving Foam semantics on the web.

That implies a separation of concerns:

  • Foam should own parsing, graph resolution, publish filtering, and content transformation.
  • The site framework should own rendering, navigation chrome, styling, and deployment ergonomics.

That boundary is what the architecture note should lock down.


User Features Backlinking (`docs/user/features/backlinking.md`)

Backlinks are one of Foam's most powerful features for knowledge discovery. They automatically show you which notes reference your current note, creating a web of interconnected knowledge that reveals surprising relationships between your ideas.

[๐Ÿ“น Watch: Understanding and using backlinks in Foam]

A backlink is a connection from another note that points to the note you're currently viewing. While you create forward links intentionally with [[wikilinks]], backlinks are discovered automatically by Foam.

Forward Links (what you create):

markdown
# Machine Learning Note

I'm studying [[Neural Networks]] and [[Deep Learning]] concepts.

Backlinks (what Foam discovers):
If you're viewing the "Neural Networks" note, Foam shows you that "Machine Learning Note" links to it, even though you didn't explicitly create that reverse connection.

This bidirectional linking creates a richer knowledge network than traditional hierarchical folders.

The Connections panel shows both forward links and backlinks:

  1. Open Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
  2. Type "connections" and select "Explorer: Focus on Connections"
  3. Use the filter buttons to show only backlinks, forward links, or all connections

[๐Ÿ“น Watch: Finding and opening the backlinks panel]

1. Finding Unexpected Connections

Backlinks often reveal relationships you didn't consciously create:

Example: While reviewing a "Productivity" note, backlinks might show connections from:

  • A cooking recipe (time management for meal prep)
  • A fitness routine (efficient workout planning)
  • A work project (team productivity strategies)

These diverse connections can spark new insights and cross-domain learning.

2. Identifying Important Concepts

Notes with many backlinks are often central to your thinking:

  • Hub concepts that connect many ideas
  • Frequently referenced resources or definitions
  • Bridge topics that span multiple domains

3. Building Context Around Ideas

Backlinks provide context for how you use concepts across different areas:

  • How you apply the same principle in various projects
  • Evolution of your thinking about a topic over time
  • Different perspectives you've encountered on the same idea

[๐Ÿ“น Watch: Using backlinks for knowledge discovery and research]


User Features Block Anchors (`docs/user/features/block-anchors.md`)

Block Anchors

Block anchors let you link to a specific paragraph, list item, heading, or blockquote within a note โ€” not just to the note as a whole or to a section heading.

Adding a Block Anchor

Place ^your-id at the end of any block element. The ID can contain letters, numbers, and hyphens.

The ^id marker is hidden in the preview โ€” it's metadata, not visible text.

Paragraph

markdown
This is an important finding from the experiment. ^key-finding

Multi-line paragraphs work too โ€” put the anchor at the end of the last line:

markdown
The first measurements were inconclusive.
After repeating the experiment, results became clear. ^experiment-result

List item

Place the anchor at the end of the item text. Foam anchors the entire item, including any sub-items:

markdown
- Mix dry ingredients thoroughly ^dry-step
  - 2 cups flour
  - 1 tsp salt
- Add wet ingredients ^wet-step

To anchor an entire list, place ^id on its own line immediately after the last item (no blank line):

markdown
- First item
- Second item
- Third item
^shopping-list

Heading

markdown
## Methodology ^methodology

The anchor applies to the heading line itself, not the entire section below it.

Blockquote

Three placements are supported:

As the last line inside the blockquote:

markdown
> The only way to do great work is to love what you do.
> ^jobs-quote

On its own line immediately after the blockquote:

markdown
> We shall fight on the beaches,
> we shall fight on the landing grounds.
^churchill-beaches

After the blockquote with a blank line (useful if your markdown formatter inserts one):

markdown
> We shall fight on the beaches,
> we shall fight on the landing grounds.

^churchill-beaches

Code block

Place ^id on its own line after the closing fence. One blank line between the fence and ^id is also accepted (useful if your markdown formatter adds one automatically):

markdown
```python
def greet(name):
    return f"Hello, {name}"
```
^greet-function

Table

Place ^id on its own line after the table. One blank line is also accepted:

markdown
| Name  | Score |
| ----- | ----- |
| Alice | 95    |
| Bob   | 87    |
^results-table

Linking to a Block

Use [[note-name#^blockid]] to link directly to a block:

markdown
[[research-notes#^key-insight]]
[[research-notes#^list-ref]]

Foam provides autocomplete for block IDs when you type #^ inside a wikilink.

You can also add display text:

markdown
[[research-notes#^key-insight|See the key insight]]

Embedding a Block

Use ![[note-name#^blockid]] to embed just that block inline:

markdown
![[research-notes#^key-insight]]

Only the referenced block's content is shown โ€” not the entire note.

Renaming a Block ID

Place your cursor on a ^blockid anchor and press F2 to rename it. Foam updates the anchor and all wikilinks that reference it across your workspace.

Diagnostics

Foam warns you when a block link points to a ^id that doesn't exist in the target note. A quick-fix lets you pick from the available block IDs.

If you accidentally use the same ^id twice in one file, Foam flags the duplicate with a warning. A quick-fix replaces it with a freshly generated unique ID.


User Features Commands (`docs/user/features/commands.md`)

Foam Commands

Foam has various commands that you can explore by calling the command palette and typing "Foam".

In particular, some commands can be very customizable and can help with custom workflows and use cases.

foam-vscode.create-note command

This command creates a note.
Although it works fine on its own, it can be customized to achieve various use cases.
Here are the settings available for the command:

  • notePath: The path of the note to create. If relative it will be resolved against the workspace root.
  • templatePath: The path of the template to use. If relative it will be resolved against the workspace root.
  • title: The title of the note (that is, the FOAM_TITLE variable)
  • text: The text to use for the note. If also a template is provided, the template has precedence
  • variables: Variables to use in the text or template
  • date: The date used to resolve the FOAMDATE* variables. in YYYY-MM-DD format
  • onFileExists?: 'overwrite' | 'open' | 'ask' | 'cancel': What to do in case the target file already exists

To customize a command and associate a key binding to it, open the key binding settings and add the appropriate configuration, here are some examples:

  • Create a note called test note.md with some text. If the note already exists, ask for a new name
text
{
  "key": "alt+f",
  "command": "foam-vscode.create-note",
  "args": {
    "text": "test note ${FOAM_DATE_YEAR}",
    "notePath": "test note.md",
    "onFileExists": "ask"
  }
}
  • Create a note following the weekly-note.md template. If the note already exists, open it
text
{
  "key": "alt+g",
  "command": "foam-vscode.create-note",
  "args": {
    "templatePath": ".foam/templates/weekly-note.md",
    "onFileExists": "open"
  }
}

foam-vscode.open-resource command

This command opens a resource.

Normally it receives a URI, which identifies the resource to open.

It is also possible to pass in a filter, which will be run against the workspace resources to find one or more matches.

  • If there is one match, it will be opened
  • If there is more than one match, a quick pick will show up allowing the user to select the desired resource

Examples:

text
{
  "key": "alt+f",
  "command": "foam-vscode.open-resource",
  "args": {
    "filter": {
      "title": "Weekly Note*"
    }
  }
}

Foam provides commands to convert between wikilink and markdown link formats.

Converts a wikilink at the cursor position to markdown link format with a relative path.

Example: [[my-note]] โ†’ [My Note](../path/to/my-note.md)

Converts a markdown link at the cursor position to wikilink format.

Example: [My Note](../path/to/my-note.md) โ†’ [[my-note]]

Usage:

  1. Place your cursor inside a wikilink or markdown link
  2. Open the command palette (Ctrl+Shift+P / Cmd+Shift+P)
  3. Type "Foam: Convert" and select the desired conversion command

User Features Custom Markdown Preview Styles (`docs/user/features/custom-markdown-preview-styles.md`)

Custom Markdown Preview Styles

Visual Studio Code allows you to use your own CSS in the Markdown preview tab.

Instructions

Custom CSS for the Markdown preview can be implemented by using the "markdown.styles": [] setting in settings.json. The stylesheets can either be https URLs or relative paths to local files in the current workspace.

For example, to load a stylesheet called Style.css, we can update settings.json with the following line:

text
{
  "markdown.styles": ["Style.css"]
}

Foam elements

It is possible to custom style the links to a note or placeholder. The links are an <a> tag. For notes use the class foam-note-link, for placeholders use foam-placeholder-link.

Cyclic inclusion warnings

Foams offers the functionality to include other notes in your note. This will be displayed in the preview tab. Foam recognises a cyclic inclusion of notes and will display a warning when detected. The following html is used and can be custom styled using the class foam-cyclic-link-warning.

html
<div class="foam-cyclic-link-warning">
  Cyclic link detected for wikilink: ${wikilink}
</div>

User Features Custom Snippets (`docs/user/features/custom-snippets.md`)

Adding Custom Snippets

You can add custom snippets whilst the default set of snippets are decided by following the below steps:

  1. Cmd + Shift + P (Ctrl + Shift + P for Windows), type snippets and select Preferences: Configure User Snippets.
  2. The command palette will remain in focus. Search for markdown and select the entry entitled markdown.json (Markdown).
  3. A JSON file will open. You can author your own snippets using the documentation to help you, or if you're using a snippet shared by another Foam user then you can copy and paste it in, as the below GIF demonstrates:

To get started, you might consider replacing the entire contents of the markdown.json file opened by the steps above with the JSON in this comment.


User Features Daily Notes (`docs/user/features/daily-notes.md`)

Daily Notes

Daily notes allow you to quickly create and access a note file for each day.

Creating Daily Notes

  • Command: Ctrl+Shift+P โ†’ "Foam: Open Daily Note"
  • Shortcut: Alt+D
  • Snippets: Type /today, /yesterday, /tomorrow in any note

Automatic Daily Notes

Open daily note automatically on VS Code startup:

json
{
  "foam.openDailyNote.onStartup": true
}

Daily Note Templates

Create .foam/templates/daily-note.md to customize the structure:

markdown
---
type: daily-note
---

# Daily Note - $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE

## Tasks

- [ ]

## Notes

Linking to the Previous Daily Note

$FOAM_PREVIOUS_DAILY_NOTE expands to the most recent daily note that exists before the one being created. It skips gaps, so after a weekend or a holiday it points at the last note you actually wrote โ€” unlike /yesterday, which is always the calendar day before.

markdown
---
type: daily-note
---

# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE

Previously: [[$FOAM_PREVIOUS_DAILY_NOTE]]

In your very first daily note there is no previous one, and the variable expands to nothing. To write something else instead, give it a fallback:

markdown
Previously: ${FOAM_PREVIOUS_DAILY_NOTE:nothing yet}

The variable is only available in the daily note template, and it finds daily notes by matching the path your template creates them at. That path has to spell out the year, month and day in numbers โ€” /journal/$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE.md works, and so does $FOAM_TITLE. A path built from a month name or a week number ($FOAM_DATE_MONTH_NAME, $FOAM_DATE_WEEK) cannot be read back, so the variable stays empty and its fallback shows instead. Daily notes written before you last changed that path are not found either. A JavaScript daily note template picks its path as it runs, so the variable is empty there too โ€” a JavaScript template can find the previous note itself.

Date Snippets

Create links to recent daily notes using snippets:

Snippet Date
/today today
/tomorrow tomorrow
/yesterday yesterday
/monday next Monday
/+1d tomorrow
/-3d 3 days ago
/+1w in a week
/-1m one month ago
/+1y in one year

Configuration

By default, daily notes are created as yyyy-mm-dd.md in the workspace's journal folder. The foam daily command uses the same location.

To customize your daily note location and format you can create a .foam/templates/daily-note.md template. See [templates] for more information.

There are also some settings to customize the behavior of daily notes, but they are deprecated and will be removed. Please use the daily-note.md template.

To work with daily notes from the terminal, see [[daily|CLI daily command]].


User Features Embeds (`docs/user/features/embeds.md`)

Note Embeds

Embeds allow you to include content from other notes directly into your current note. This is powerful for creating dynamic content that updates automatically when the source note changes.

If you want to embed a dynamic list or table of notes instead of the content of one note, see [foam-queries].

Basic Syntax

Use the embed syntax with an exclamation mark before the wikilink:

markdown
![[note-name]]

This will embed the entire content of note-name into your current note.

Embedding Sections

You can embed specific sections of a note by referencing the heading:

markdown
![[note-name#Section Title]]

Embedding Blocks

Embed a specific paragraph, list item, heading, or blockquote using a block anchor:

markdown
![[note-name#^blockid]]

See [block-anchors] for how to add ^id markers to your notes.

Embed Types

Foam supports different embedding scopes and styles that can be configured globally or overridden per embed.

Scope Modifiers

  • full - Include the entire note or section, including the title/heading
  • content - Include everything except the title: the note's first # heading, or the section's own heading

Examples:

markdown
full![[my-note]] # Include title + content
content![[my-note]] # Content only, no title

Style Modifiers

  • card - Display the embedded content in a bordered container
  • inline - Display the content seamlessly as part of the current note

Examples:

markdown
card![[my-note]] # Bordered container
inline![[my-note]] # Seamless integration

Custom Card Styling

Customize the appearance of the bordered container in markdown preview by adding CSS:

  1. Create .foam/css/custom-styles.css
  2. Add CSS targeting .embed-container-note class:
    css
    .embed-container-note {
       border: 1px solid #d1d9e0;
       box-sizing: border-box;
       padding: 4px 16px;
       border-radius: 6px;
    }
  3. Update .vscode/settings.json:
    json
    {
      "markdown.styles": [".foam/css/custom-styles.css"]
    }

Combined Modifiers

You can combine scope and style modifiers:

markdown
full-card![[my-note]] # Title + content in bordered container
content-inline![[my-note]] # Content only, seamlessly integrated
full-inline![[my-note]] # Title + content, seamlessly integrated
content-card![[my-note]] # Content only in bordered container

Configuration

Set your default embed behavior in VS Code settings:

json
{
  "foam.preview.embedNoteType": "full-card"
}

Available options:

  • full-card (default)
  • full-inline
  • content-card
  • content-inline

Image Sizing

Resize images to make your documents more readable:

markdown
![[image.png|300]]          # 300 pixels wide
![[image.png|50%]]          # Half the container width

Common Use Cases

Make large screenshots readable:

markdown
![[screenshot.png|600]]

Create responsive images:

markdown
![[diagram.png|70%]]

Size by width and height:

markdown
![[image.png|300x200]]

Alignment

Center, left, or right align images:

markdown
![[image.png|300|center]]
![[image.png|300|left]]
![[image.png|300|right]]

Alt Text

Add descriptions for accessibility:

markdown
![[chart.png|400|Monthly sales chart]]

Units

  • 300 or 300px - pixels (default)
  • 50% - percentage of container
  • 20em - relative to font size

Troubleshooting

  • Check image path: ![[path/to/image.png|300]]
  • No spaces around pipes: |300| not | 300 |
  • Images only resize in preview mode, not edit mode
  • Use lowercase alignment: center not Center

User Features Foam Queries (`docs/user/features/foam-queries.md`)

Foam Queries

A Foam Query selects notes by tag, link, property, or other criteria. Queries answer questions like "show my research notes", "list notes linked to this topic", or "count notes in this folder".

Queries are surfaced in two places:

  • Inline in a note โ€” write a foam-query block to render results in the Markdown preview.
  • Saved on disk โ€” drop a YAML file in .foam/queries/ to reuse the same query across contexts. VS Code surfaces saved queries as [smart-folders] in the side bar.

The query syntax is the same in both. The rest of this page documents that syntax.

For static note embeds, see [embeds].

Saved Queries

Save a query as a YAML file at .foam/queries/.yaml to reuse it. The filename (without extension) is the id; rename the file to rename the query.

A saved query inlines the query fields at the root of the YAML, plus two optional wrapper fields:

yaml
name: Work in Progress           # optional; defaults to a humanized form of the filename
description: Notes I am editing  # optional
filter:
  and:
    - tag: "#wip"
    - not:
        tag: "#archive"
sort: title ASC
limit: 50

The minimal valid file is one line: filter: "#wip".

A saved query file is interchangeable with a foam-query block โ€” copy the body of one straight into the other.

Basic Query

Use a foam-query code block:

markdown
```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:

markdown
```foam-query
format: count
```

Query Options

  • 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.

Filters

Simple Filters

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:

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

Structured Filters

Use YAML when you need more control:

markdown
```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: the kind of file Foam detected โ€” note for markdown files, image or attachment for everything else (e.g. type: "attachment"). This is not the type you write in a note's frontmatter โ€” to filter on that, use jexl
  • 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", or "resource.properties.type == 'daily-note'" to match notes by their frontmatter type. 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:
markdown
```foam-query
filter:
  links_to: "$current"
```

Displayed Fields

You can select these fields:

  • title โ€” frontmatter title if set, otherwise the first H1 heading, otherwise the filename
  • path โ€” full workspace-relative path including the filename and extension
  • filename โ€” the file's name without the extension. Useful when title is overridden by frontmatter or an H1 and you still want the on-disk name
  • folder โ€” the containing folder, e.g. /projects
  • extension โ€” the file extension including the leading dot, e.g. .md, or empty string if none
  • 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:

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

Including note content in results

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

markdown
```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:

markdown
```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-sensitively โ€” section[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.

Customising table column headers

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:

markdown
```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.

Making cells clickable

By default the title column is rendered as a clickable link to the matching note. Every other field is rendered as plain text. You can override that on a per-entry basis with link:

markdown
```foam-query
filter: "#projects"
format: table
select:
  - field: title
    link: false             # render title as plain text
  - field: filename
    link: true              # make filename clickable instead
  - properties.Status
```

link: true and link: false work for any field, and the link target is always the row's note.

Count Queries

Use count when you only need the number of matches:

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

JavaScript Queries

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.

markdown
```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:

markdown
```foam-query-js
// Show all notes that link to this note
render(foam.pages({ links_to: foam.current }).sortBy('title'));
```
markdown
```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?, link? } to customise the column header or toggle the per-cell link, e.g. .select(['title', { field: 'filename', link: true }, { 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.

Trust And Limitations

  • 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.

User Features Footnotes (`docs/user/features/footnotes.md`)

Footnotes

Footnotes let you add references or side notes to your writing without cluttering the main text.

Writing Footnotes

Use standard Markdown footnote syntax:

markdown
The study confirmed the hypothesis.[^1] Further work is needed.[^note]

[^1]: Smith et al., 2023, Journal of Examples.
[^note]: See the appendix for the full methodology.

Footnote IDs can be numbers or words. The definition can appear anywhere in the file โ€” Foam finds it regardless of position.

Hover over a footnote reference like [^1] to see its definition inline.

Press F12 (or Ctrl+Click / Cmd+Click) on a reference to jump to its definition in the file.

Appearance

Footnote references are styled to match wikilinks โ€” the ^id text is colored. In Preview, footnotes are rendered at the bottom of the note.

Multiple References

You can reference the same footnote more than once:

markdown
This point[^caveat] is elaborated elsewhere.[^caveat]

[^caveat]: See the companion article for full details.

User Features Graph View (`docs/user/features/graph-view.md`)

Graph Visualization

The graph view transforms your notes into a visual network, revealing connections between ideas. To open it, run the Foam: Show Graph command.

Files and tags appear as nodes; links between files and file-to-tag relationships are the edges. A node grows in size with the number of connections it has.

The `Show Graph` command

  1. Press Ctrl+Shift+P / Cmd+Shift+P
  2. Type "Foam: Show Graph"
  3. Press Enter

You can set up a custom keyboard shortcut:

  1. Go to File > Preferences > Keyboard Shortcuts
  2. Search for "Foam: Show Graph"
  3. Assign your preferred shortcut

Graph Navigation

With the Foam graph visualization you can:

  • highlight a node by hovering on it, to quickly see how it's connected to the rest of your notes
  • select one or more (by keeping shift pressed while selecting) nodes by clicking on them, to better understand the structure of your notes
  • navigate to a note by clicking on it's node while pressing ctrl or cmd
  • automatically center the graph on the currently edited note, to immediately see its connections

Preview Mode

By default, clicking a node opens the source file in the editor. To open the markdown preview instead, enable the foam.graph.navigateToPreview setting:

json
"foam.graph.navigateToPreview": true

This gives you a two-panel layout โ€” graph on one side, rendered preview on the other โ€” without a source editor in between. Non-markdown files (attachments, images, etc.) always open in the editor regardless of this setting.

Groups

The Groups panel (top-right of the graph) controls which nodes are visible and how they are colored. It has three parts:

Color by โ€” sets the default coloring strategy for nodes:

  • None โ€” each node type gets its own color
  • Type โ€” colors by the note's type frontmatter property
  • Directory โ€” colors by the directory the file lives in

Built-in types โ€” checkboxes and color dots for tag, attachment, image, and placeholder nodes. Uncheck to hide, click the colored dot to change the color.

Custom groups โ€” rules you define to color (and optionally hide) a subset of notes. Each group matches notes by a property and assigns a color. Click + Add group to create one.

A group matches notes by:

  • type โ€” exact match on the note's type (e.g. project)
  • path โ€” substring match on the file path (e.g. journal)
  • tag โ€” exact match on a tag (e.g. daily)
  • title โ€” substring match on the note title
  • Any frontmatter key โ€” exact match on a custom property

Use /regex/ syntax for pattern matching: e.g. /^2024/ matches paths starting with 2024.

Groups layer on top of the default coloring โ€” last matching group wins. Unchecking a group hides notes that only belong to that group.

Named Views

Define pre-configured graph views in foam.graph.views. A view named "Default" is applied automatically whenever the graph opens โ€” use it to set your preferred starting configuration.

json
"foam.graph.views": [
  {
    "name": "Default",
    "colorBy": "directory",
    "show": {
      "tag": { "enabled": false },
      "placeholder": { "enabled": false }
    }
  },
  {
    "name": "Journal",
    "colorBy": "directory",
    "show": {
      "tag": { "enabled": false },
      "placeholder": { "enabled": false }
    },
    "groups": [
      {
        "id": "journal",
        "label": "path=journal",
        "color": "#6bcb77",
        "enabled": true,
        "match": { "property": "path", "value": "journal" }
      }
    ]
  }
]

Open a named view via a keybinding in keybindings.json:

json
{
  "key": "ctrl+shift+j",
  "command": "foam-vscode.show-graph",
  "args": { "view": "Journal" }
}

You can also pass a config inline without a named view:

json
{
  "key": "ctrl+shift+g",
  "command": "foam-vscode.show-graph",
  "args": {
    "config": {
      "colorBy": "type",
      "show": { "placeholder": { "enabled": false } }
    }
  }
}

View config fields:

Field Description
name Display name shown in the panel title. Use "Default" to apply automatically on open.
colorBy "none", "directory", or "type"
groups Array of custom group rules (see Groups section above)
show Per built-in type config: { "tag": { "enabled": true, "color": "#ff0000" } }
background Background color override
fontSize Font size override
fontFamily Font family override
lineColor Edge color override

All fields are optional. When both view and config are provided, config is merged on top of the named view.

What's Next?

With graph view mastery, you're ready to explore advanced Foam features:

  1. [wikilinks] - Understand bidirectional connections
  2. [templates] - Use templates effectively to standardize your note creation
  3. [tags] - Organize your notes with tags
  4. [daily-notes] - Set up daily notes to establish capture routines

Legacy: `foam.graph.style`

Deprecated. Configure the graph via foam.graph.views instead (see above). Define a "Default" view to replace any settings you have in foam.graph.style.

foam.graph.style still works and is applied as the base layer before any view config:

json
"foam.graph.style": {
    "background": "#202020",
    "fontSize": 12,
    "fontFamily": "Sans-Serif",
    "lineColor": "#277da1",
    "lineWidth": 0.2,
    "particleWidth": 1.0,
    "highlightedForeground": "#f9c74f",
    "node": {
        "note": "#277da1",
        "placeholder": "#545454",
        "tag": "#f9c74f"
    }
}

Link reference definitions make your notes compatible with standard Markdown processors by converting wikilinks to standard Markdown references.

Foam doesn't need references in order to work, but this feature is aimed at supporting other tools you might want to integrate with.

Foam can automatically add reference definitions to the bottom of your notes:

Your note:

markdown
# Machine Learning

Related to [[Data Science]] and [[Statistics]].

With reference definitions:

markdown
# Machine Learning

Related to [[Data Science]] and [[Statistics]].

[Data Science]: data-science.md 'Data Science'
[Statistics]: statistics.md 'Statistics'

Enabling Reference Definitions

Configure in your settings:

json
{
  "foam.edit.linkReferenceDefinitions": "withExtensions"
}

Options:

  • "off" - Disabled (default)
  • "withoutExtensions" - References without extension
  • "withExtensions" - References with extension

If you are using your notes only within Foam, you can keep definitions off (also to reduce clutter), otherwise pick your setting based on what is required by your use case.

How It Works

  1. Scans your note for wikilinks
  2. Generates reference definitions when you save
  3. Updates definitions when links change
  4. Maintains the auto-generated section

Benefits

  • Standard Markdown compatibility - Works with any Markdown processor
  • Publishing platforms - Compatible with GitHub Pages, Jekyll, etc.
  • Future-proofing - Not locked into Foam-specific format
  • Team collaboration - Others can read notes without Foam

User Features Note Properties (`docs/user/features/note-properties.md`)


type: feature keywords: hello world, bonjour tags: [hello, bonjour]

Note Properties

At the top of the file you can have a section where you define your properties. This section is known as the Front-Matter of the document and uses YAML formatting.

Be aware that this YAML section needs to be at the very top of the file to be valid.

For example, for this file, we have:

markdown
---
type: feature
keywords: hello world, bonjour
---

This sets the type of this document to feature and sets three keywords for the document: hello, world, and bonjour. The YAML parser will treat both spaces and commas as the separators for these YAML properties. If you want to use multi-word values for these properties, you will need to combine the words with dashes or underscores (i.e. instead of hello world, use hello_world or hello-world).

You can set as many custom properties for a document as you like, but there are a few special properties defined by Foam.

Special Properties

Some properties have special meaning for Foam:

Name Description
title will assign the name to the note that you will see in the graph, regardless of the filename or the first heading (also see how to [note-taking-in-foam])
type can be used to style notes differently in the graph (also see [graph-view]). The default type for a document is note unless otherwise specified with this property.
tags can be used to add tags to a note (see [tags])
alias can be used to add aliases to the note. an alias will show up in the link autocompletion

For example:

markdown
---
title: "Note Title"
type: "daily-note"
tags: daily, funny, planning
alias: alias1, alias2
---

Foam Template Properties

There also exists properties that are even more specific to Foam templates, see [templates#Metadata] for more info.


User Features Paste Images From Clipboard (`docs/user/features/paste-images-from-clipboard.md`)

Paste Images from Clipboard

By installing the vscode-paste-image extension, you can paste an image from the clipboard with cmd+alt+v.

Images are automatically copied to the /attachments folder and a reference is added in the file where you pasted them.

A prompt will ask you to confirm the name of the image, to disable it set "pasteImage.showFilePathConfirmInputBox": false, in the settings.

To change the location where the image is created, change the pasteImage.path property, e.g.:

  • ${currentFileDir}: save the image next to the file
  • ${currentFileDir}/images: create an images directory next to the file and save the image there

User Features Resource Filters (`docs/user/features/resource-filters.md`)

Resource Filters

Resource filters can be passed to some Foam commands to limit their scope.

A filter supports the following parameters:

  • tag: include a resource if it has the given tag (e.g. {"tag": "#research"})
  • type: include a resource if it is of the given type. Foam assigns note to every markdown file, and image or attachment to other files (e.g. {"type": "attachment"}). This is not the type you write in a note's frontmatter โ€” to filter on that, use jexl
  • path: include a resource if its path matches the given regex (e.g. {"path": "/projects/*"}). Note that this parameter supports regex and not globs.
  • jexl: include a resource if the given Jexl expression is truthy, where resource represents the resource being evaluated (e.g. {"jexl": "resource.properties.type == 'weekly-note'"} to match notes by their frontmatter type)
  • title: include a resource if the title matches the given regex (e.g. {"title": "Team meeting:*"})

A filter also supports some logical operators:

  • and: include a resource if it matches all the sub-parameters (e.g {"and": [{"tag": "#research"}, {"title": "Paper *"}]})
  • or: include a resource if it matches any of the sub-parameters (e.g {"or": [{"tag": "#research"}, {"title": "Paper *"}]})
  • not: invert the result of the nested filter (e.g. {"not": {"type": "attachment"}})

Here is an example of a complex filter, to open a resource from a subset of the workspace:

json
{
  "key": "alt+f",
  "command": "foam-vscode.open-resource",
  "args": {
    "filter": {
      "and": [
        {
          "or": [
            { "jexl": "resource.properties.type == 'daily-note'" },
            { "jexl": "resource.properties.type == 'weekly-note'" },
            { "path": "/projects/*" }
          ]
        },
        { "not": { "tag": "#b" } }
      ]
    }
  }
}

User Features Smart Folders (`docs/user/features/smart-folders.md`)

Smart Folders

Smart Folders are how Foam surfaces your saved queries in the VS Code side bar. Each Smart Folder is a query stored at .foam/queries/.yaml โ€” see [foam-queries] for the query syntax.

Use them to navigate large workspaces without locking yourself into rigid tag hierarchies. For example: a "Work in Progress" folder showing notes tagged #wip but not #archive.

Create a Smart Folder

Open the Smart Folders panel in the Explorer and click Create Smart Folder, or run the command Foam: Create Smart Folder.

You will be asked for:

  1. A name (e.g. "Work in Progress")
  2. Tags to include (multi-select โ€” press Esc to skip and edit the YAML manually)

Foam creates a YAML file at .foam/queries/<name>.yaml and opens it for editing.

Edit a Smart Folder

Click the pencil icon next to a Smart Folder in the panel, or open the YAML file directly. Changes are picked up automatically.

For the full syntax โ€” structured filters, sorting, links, Jexl expressions โ€” see [foam-queries].

Delete a Smart Folder

Click the trash icon next to a Smart Folder in the panel and confirm. The YAML file is removed.

View options

In the panel title bar:

  • Group By Folder / Flat list โ€” group matching notes by their workspace path or show as a flat list
  • Refresh โ€” re-run all queries on demand

User Features Spell Checking (`docs/user/features/spell-checking.md`)

Spell Checking

There are many spell checking extensions for VS Code.

The most popular spell checker for VS Code is Code Spell Checker.

Another one of our favorites is LTeX, which is a bit heavier but offers some extra functionality.

Another popular one is Spellright, but be mindful that there have been reports of incompatibility with the vscode-markdown extension (see https://github.com/foambubble/foam/issues/1068).


User Features Tags (`docs/user/features/tags.md`)

Tags

Tags provide flexible categorization and organization for your notes beyond wikilinks and folders.

Creating Tags

Inline Tags

Add tags directly in note content:

markdown
# Machine Learning Fundamentals

This covers basic algorithms and applications.

#machine-learning #data-science #algorithms #beginner

Front Matter Tags

Add tags in YAML front matter:

markdown
---
tags: [machine-learning, data-science, algorithms, beginner]
---

Hierarchical Tags

Create tag hierarchies using forward slashes:

markdown
#programming/languages/python
#programming/frameworks/react
#work/projects/website-redesign
#personal/health/exercise

Autocompletion

Typing # shows existing tags. In front matter, use Ctrl+Space for tag suggestions.

Tag Explorer

Use the Tag Explorer panel in VS Code's sidebar to:

  • Browse hierarchical tag structure
  • Filter by tag names
  • Click tags to see all associated notes
  • View tag usage counts
  • Search for tags (click the search icon or use "Foam: Search Tag" command)

Tags also appear in the [graph-view] with customizable colors.

Search for all occurrences of a tag across your workspace:

  1. Use the command palette: "Foam: Search Tag"
  2. Or click the search icon next to a tag in the Tag Explorer panel

Results appear in VS Code's search panel where you can navigate between matches.

Known limitation: this command leverages VS Code's search capability, so it's constrained by its use of regular expressions. The search is best-effort and some false search results might show up.

Custom Tag Styling

Customize tag appearance in markdown preview by adding CSS:

  1. Create .foam/css/custom-tag-style.css
  2. Add CSS targeting .foam-tag class:
    css
    .foam-tag {
      color: #ffffff;
      background-color: #000000;
    }
  3. Update .vscode/settings.json:
    json
    {
      "markdown.styles": [".foam/css/custom-tag-style.css"]
    }

Some users prefer backlinks (e.g. linking to a book note) instead of #book tags for categorization. Both approaches work โ€” choose what fits your workflow.

To manage tags from the terminal, see [[tag|CLI tag command]].


User Features Templates (`docs/user/features/templates.md`)

Note Templates

Foam supports note templates which let you customize the starting content of your notes instead of always starting from an empty note.

Foam supports two types of templates:

  • Markdown templates (.md files) - Simple templates with predefined content and variables
  • JavaScript templates (.js files) - Smart templates that can adapt based on context and make intelligent decisions

Both types of templates are located in the special .foam/templates directory of your workspace.

Quickstart

Creating templates

For simple templates:

  • Run the Foam: Create New Template command from the command palette
  • OR manually create a regular .md file in the .foam/templates directory

For smart templates:

Using templates

To create a note from a template:

  • Run the Foam: Create New Note From Template command and follow the instructions. Don't worry if you've not created a template yet! You'll be prompted to create a new simple template if none exist.
  • OR run the Foam: Create New Note command, which uses the special default template (.foam/templates/new-note.md or .foam/templates/new-note.js, if it exists)

Multi-root workspaces

If your VS Code workspace has several folders, see [multi-root-workspaces] for where Foam looks for templates and how to create notes in a specific folder.

Special templates

Default template

The default template is used by the Foam: Create New Note command. Foam will look for these templates in order:

  1. .foam/templates/new-note.js (JavaScript template)
  2. .foam/templates/new-note.md (Markdown template)

Customize this template to contain content that you want included every time you create a note.

Default daily note template

The daily note template is used when creating daily notes (e.g. by using Foam: Open Daily Note). Foam will look for these templates in order:

  1. .foam/templates/daily-note.js (JavaScript template)
  2. .foam/templates/daily-note.md (Markdown template)

For a simple markdown template, it is recommended to define the YAML Front-Matter similar to the following:

markdown
---
type: daily-note
---

JavaScript Templates

JavaScript templates are a powerful way to create smart, context-aware note templates that can adapt based on the situation. Unlike static Markdown templates, JavaScript templates can make intelligent decisions about what content to include.

Use JavaScript templates when you want to:

  • Create different note structures based on the day of the week, time, or date
  • Adapt templates based on where the note is being created from
  • Automatically find and link related notes in your workspace
  • Generate content based on existing notes or workspace structure
  • Implement complex logic that static templates cannot handle

Basic JavaScript template structure

A JavaScript template is a .js file that exports a function returning note content, and optionally location:

javascript
// .foam/templates/daily-note.js
async function createNote({ trigger, foam, resolver, foamDate }) {
  const today = dayjs();
  // or you could use foamDate for day specific notes, see FOAM_DATE_* variables
  // const day = dayjs(foamDate)
  const formattedDay = today.format('YYYY-MM-DD');

  // if you need a variable you can use the resolver
  // const title = await resolver.resolveFromName('FOAM_TITLE');

  console.log('Creating note for today: ' + formattedDay, JSON.stringify(trigger));

  let content = `# Daily Note - ${formattedDay}

## Today's focus
- 

## Notes
- 
`;

  switch (today.day()) {
    case 1: // Monday
      content = `# Week Planning - ${formattedDay}

## This week's goals
- [ ] Goal 1
- [ ] Goal 2

## Focus areas
- 
`;
      break;
    case 5: // Friday
      content = `# Week Review - ${formattedDay}

## What went well
- 

## What could be improved
- 

## Next week's priorities
- 
`;
      break;
  }

  return {
    content,
    filepath: `/weekly-planning/${formattedDay}.md`,
  };
}

Examples

Smart meeting notes:

javascript
async function createNote({ trigger, foam, resolver }) {
  const title = (await resolver.resolveFromName('FOAM_TITLE')) || 'Meeting';
  const today = dayjs();
  // Detect meeting type from title
  const isStandup = title.toLowerCase().includes('standup');
  const isReview = title.toLowerCase().includes('review');

  let template = `# ${title} - ${today.format('YYYY-MM-DD')}

`;

  if (isStandup) {
    template += `## What I did yesterday
- 

## What I'm doing today
- 

## Blockers
- 
`;
  } else if (isReview) {
    template += `## What went well
- 

## What could be improved
- 

## Action items
- [ ] 
`;
  } else {
    template += `## Agenda
- 

## Notes
- 

## Action items
- [ ] 
`;
  }

  return {
    content: template,
    filepath: `/meetings/${title}.md`,
  };
}

Template result format

JavaScript templates must return an object with:

  • content (required): The note content as a string. Foam variables in it (e.g. ${FOAM_TITLE}) are resolved like in a Markdown template
  • filepath (required): Custom file path for the note
    • NOTE: the path must be within the workspace.
      • A relative path will be resolved based on the onRelativePath command configuration.
      • An absolute path will be taken as is, if it falls within the workspace. Otherwise it will be considered to be from the workspace root
javascript
return {
  content: '# My Note\n\nContent here...',
  filepath: 'custom-folder/my-note.md',
};

Tab stops

VS Code tab stops work in content, but ${...} is also JavaScript template literal syntax. Escape the $ so the tab stop reaches the note:

javascript
const content = `# ${title}

## Mood
\${1:great}

## To-dos
- [ ] \${2}
`;

Security and limitations

JavaScript templates execute real JavaScript. Foam guards where and when
they run, but the in-process sandbox is not a security boundary โ€” a
malicious template can escape it. Trust controls are the real protection:

  • โœ… Only run in trusted VS Code workspaces
  • โœ… When using the foam CLI, only run with the --trust flag
  • โŒ Never run under the MCP server. foam mcp and the
    create_resource tool refuse .js templates with an
    untrusted_workspace error
  • โฑ 10-second execution timeout

โš ๏ธ **Treat a new-note.js like a script you'd execute by hand.
** Only use JS templates from workspaces whose contributors you trust.

If you don't need the power of arbitrary JavaScript, prefer a Markdown
template โ€” those run everywhere (CLI, MCP, web extension) and have no
trust requirement.

Markdown templates

Markdown templates are a simple way to create notes

Use Markdown templates when you want to:

  • Create simple, consistent note structures
  • Use basic variables and placeholders
  • Keep templates easy to read and modify

Variables

Markdown templates can use all the variables available in VS Code Snippets.

In addition, you can also use variables provided by Foam:

Name Description
FOAM_SELECTED_TEXT Foam will fill it with selected text when creating a new note, if any text is selected. Selected text will be replaced with a wikilink to the new
FOAM_TITLE The title of the note. If used, Foam will prompt you to enter a title for the note.
FOAM_TITLE_SAFE The title of the note in a file system safe format. If used, Foam will prompt you to enter a title for the note unless FOAM_TITLE has already caused the prompt.
FOAM_SLUG The sluggified title of the note (using the default github slug method). If used, Foam will prompt you to enter a title for the note unless FOAM_TITLE has already caused the prompt.
FOAM_CURRENT_DIR The current editor's directory path. Resolves to the directory of the currently active file, or falls back to workspace root if no editor is active. Useful for creating notes in the current directory context.
FOAM_PREVIOUS_DAILY_NOTE The most recent daily note that exists before the one being created, skipping any gap. Daily note templates only. See [daily-notes].
FOAM_DATE_FORMAT The Foam date formatted using a dayjs format string. Defaults to ISO 8601 with local timezone offset (e.g. 2026-03-12T22:06:55+01:00). Use as $FOAM_DATE_FORMAT for the default, or ${FOAM_DATE_FORMAT:YYYY-MM-DD} for a custom format.
FOAM_DATE_* FOAM_DATE_YEAR, FOAM_DATE_MONTH, FOAM_DATE_WEEK, FOAM_DATE_DAY_ISO etc. Foam-specific versions of VS Code's datetime snippet variables. Prefer these versions over VS Code's.

Tab stops

Use VS Code tab stops to jump the cursor through the new note with Tab:

markdown
# $FOAM_TITLE

## Mood
${1:great}

## To-dos
- [ ] ${2}

`FOAM_DATE_FORMAT` variable

FOAM_DATE_FORMAT lets you format the Foam date using a dayjs format string:

  • $FOAM_DATE_FORMAT โ€” ISO 8601 local datetime with timezone offset, e.g. 2026-03-12T22:06:55+01:00
  • ${FOAM_DATE_FORMAT:YYYY-MM-DD} โ€” date only, e.g. 2026-03-12
  • ${FOAM_DATE_FORMAT:HH:mm} โ€” time only, e.g. 22:06

The format string (the part after :) uses dayjs tokens. Common tokens: YYYY (4-digit year), MM (month), DD (day), HH (hour), mm (minute), ss (second), Z (timezone offset).

Like all FOAM_DATE_* variables, this uses the Foam date rather than the current time, so it works correctly with relative daily notes (e.g. /tomorrow).

`FOAM_DATE_*` variables

Foam defines its own set of datetime variables that have a similar behaviour as VS Code's datetime snippet variables.

Supported variables include:

  • FOAM_DATE_YEAR: 4-digit year (e.g. 2025)
  • FOAM_DATE_MONTH: 2-digit month (e.g. 09)
  • FOAM_DATE_WEEK: ISO 8601 week number (e.g. 37)
  • FOAM_DATE_WEEK_YEAR: the year of the ISO 8601 week number. The year that contains the Thursday of the current week, may vary from calendar year near Jan 1. Often used with FOAM_DATE_WEEK.
  • FOAM_DATE_DAY_ISO: ISO 8601 weekday number (1-7, where Monday=1, Sunday=7)
  • FOAM_DATE_DATE: 2-digit day of month (e.g. 15)
  • FOAM_DATE_DAY_NAME: Full weekday name (e.g. Monday)
  • FOAM_DATE_DAY_NAME_SHORT: Short weekday name (e.g. Mon)
  • FOAM_DATE_HOUR, FOAM_DATE_MINUTE, FOAM_DATE_SECOND, FOAM_DATE_SECONDS_UNIX, etc.

For example, FOAM_DATE_YEAR has the same behaviour as VS Code's CURRENT_YEAR, FOAM_DATE_SECONDS_UNIX has the same behaviour as CURRENT_SECONDS_UNIX, etc. FOAM_DATE_DAY_ISO returns the ISO weekday number (Monday=1, Sunday=7), which is useful for ISO week date formats like 2025-W37-5.

By default, prefer using the FOAM_DATE_ versions. The datetime used to compute the values will be the same for both FOAM_DATE_ and VS Code's variables, with the exception of the creation notes using the daily note template.

For more nitty-gritty details about the supported date formats, see here.

Relative daily notes

When referring to daily notes, you can use the relative snippets (/+1d, /tomorrow, etc.). In these cases, the new notes will be created with the daily note template, but the datetime used should be the relative datetime, not the current datetime.
By using the FOAM_DATE_ versions of the variables, the correct relative date will populate the variables, instead of the current datetime.

For example, given this daily note template (.foam/templates/daily-note.md):

markdown
# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE

## Here's what I'm going to do today

- Thing 1
- Thing 2

When the /tomorrow snippet is used, FOAM_DATE_ variables will be populated with tomorrow's date, as expected.
If instead you were to use the VS Code versions of these variables, they would be populated with today's date, not tomorrow's, causing unexpected behaviour.

When creating notes in any other scenario, the FOAM_DATE_ values are computed using the same datetime as the VS Code ones, so the FOAM_DATE_ versions can be used in all scenarios by default.

Metadata

Markdown templates can also contain metadata about the templates themselves. The metadata is defined in YAML "Frontmatter" blocks within the templates.

Name Description
filepath The filepath to use when creating the new note. If the filepath is a relative filepath, it is relative to the current workspace.
name A human readable name to show in the template picker.
description A human readable description to show in the template picker.

Foam-specific variables (e.g. $FOAM_TITLE) can be used within template metadata. However, VS Code snippet variables are (currently) not supported.

`filepath` attribute

In a restricted workspace, notes must be created inside the workspace: a filepath that points outside it is refused. Trust the workspace to allow it.

It is possible to vary the filepath value based on the current date using the FOAM_DATE_* variables. This is especially useful for the [daily-notes] template if you wish to organize by years, months, etc. Below is an example of a daily-note template metadata section that will create new daily notes under the journal/YEAR/MONTH-MONTH_NAME/ filepath. For example, when a note is created on November 15, 2022, a new file will be created at C:\Users\foam_user\foam_notes\journal\2022\11-Nov\2022-11-15-daily-note.md. This method also respects the creation of daily notes relative to the current date (i.e. /+1d).

markdown
---
type: daily-note
foam_template:
  description: Daily Note
  filepath: '/journal/$FOAM_DATE_YEAR/$FOAM_DATE_MONTH-$FOAM_DATE_MONTH_NAME_SHORT/$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE-daily-note.md'
---

# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE Daily Notes

Creating notes in the current directory

To create notes in the same directory as your currently active file, use the FOAM_CURRENT_DIR variable in your template's filepath:

markdown
---
foam_template:
  name: Current Directory Note
  filepath: '$FOAM_CURRENT_DIR/$FOAM_SLUG.md'
---

# $FOAM_TITLE

$FOAM_SELECTED_TEXT

Best practices for filepath patterns:

  • Explicit current directory: $FOAM_CURRENT_DIR/$FOAM_SLUG.md - Creates notes in the current editor's directory
  • Workspace root: /$FOAM_SLUG.md - Always creates notes in workspace root
  • Subdirectories: $FOAM_CURRENT_DIR/meetings/$FOAM_SLUG.md - Creates notes in subdirectories relative to current location

The FOAM_CURRENT_DIR approach is recommended over relative paths (like ./file.md) because it makes the template's behavior explicit and doesn't depend on configuration settings.

`name` and `description` attributes

These attributes provide a human readable name and description to be shown in the template picker (e.g. When a user uses the Foam: Create New Note From Template command):

Adding template metadata to an existing YAML Frontmatter block

If your template already has a YAML Frontmatter block, you can add the Foam template metadata to it.

Foam only supports adding the template metadata to YAML Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.

Further, the template metadata must be provided as a YAML block mapping, with the attributes placed on the lines immediately following the foam_template line:

yaml
---
existing_frontmatter: "Existing Frontmatter block"
foam_template: # this is a YAML "Block" mapping ("Flow" mappings aren't supported)
  name: My Note Template # Attributes must be on the lines immediately following `foam_template`
  description: This is my note template
  filepath: `journal/$FOAM_TITLE.md`
---
This is the rest of the template

Adding template metadata to its own YAML Frontmatter block

You can add the template metadata to its own YAML Frontmatter block at the start of the template:

yaml
---
foam_template:
  name: My Note Template
  description: This is my note template
  filepath: 'journal/$FOAM_TITLE.md'
---
This is the rest of the template

If the note already has a Frontmatter block, a Foam-specific Frontmatter block can be added to the start of the template. The Foam-specific Frontmatter block must always be placed at the very beginning of the file, and only whitespace can separate the two Frontmatter blocks.

yaml
---
foam_template:
  name: My Note Template
  description: This is my note template
  filepath: 'journal/$FOAM_TITLE.md'
---

---
existing_frontmatter: 'Existing Frontmatter block'
---
This is the rest of the template

Wikilinks are internal links that connect files in your knowledge base using [[double bracket]] syntax.

  1. Type [[ and start typing a note name
  2. Select from autocomplete and press Tab
  3. Navigate with Ctrl+Click (Cmd+Click on Mac) or F12
  4. Create new notes by clicking on non-existent wikilinks

Example: [graph-view]

Placeholders

Wikilinks to non-existent files create placeholder links, styled differently to show they need files created. They're useful for planning your knowledge structure.

View placeholders in the graph with Foam: Show Graph command or in the Placeholders panel.

Link to specific sections using [[note-name#Section Title]] syntax. Foam provides autocomplete for section titles.

Examples:

  • External file: [link text](other-file.md#section-name)
  • Same document: [link text](#section-name)

Link to a specific paragraph, list item, heading, or blockquote using [[note-name#^blockid]] syntax. Add a ^your-id anchor at the end of any block element, then reference it from other notes.

See [block-anchors] for full details.

Linking to a folder name navigates to that folder's index file โ€” either index.md or README.md. This works for both wikilinks and regular markdown links:

  • [[projects]] โ†’ opens projects/index.md (or projects/README.md)
  • [Projects](projects) โ†’ same
  • [Projects](projects/) โ†’ trailing slash is ignored

If a file named projects.md exists alongside the projects/ folder, it takes priority.

To disable this behavior, set foam.links.directory.mode to disabled in your VS Code settings.

When you rename or move a note or folder, Foam automatically updates all wikilinks pointing to it. This is enabled by default and can be turned off via the foam.links.sync.enable setting.

For standard markdown links (e.g. [text](path/to/note.md)), VS Code has a built-in feature that handles this. Enable it in VS Code settings: set markdown.updateLinksOnFileMove.enabled to always or prompt.

Wikilinks come in two forms:

  • Identifier links โ€” [[filename]], [[folder/filename]] โ€” identify a resource by name, resolved relative to the whole workspace
  • Path links โ€” [[./file]], [[../other/file]], [[/from/root]] โ€” identify a resource by its file path

The rule: if the link starts with / or ., it's a path reference; otherwise it's an identifier.

For identifier links, you can use any suffix that uniquely identifies the file. Given projects/house/todo.md and work/todo.md, the identifiers [[todo]] (ambiguous), [[house/todo]] (unique), and [[projects/house/todo]] (unique) are all valid โ€” Foam picks the shortest unambiguous form.

When the same filename exists in multiple locations, [[todo]] is ambiguous. Foam resolves it alphabetically (deterministic), and shows a warning diagnostic so you can use a more specific identifier like [[house/todo]].

Markdown Compatibility

Foam can automatically generate [link-reference-definitions] at the bottom of files to make wikilinks compatible with standard Markdown processors.

Compatibility with Other Apps

Wikilink Obsidian Foam
[[notes]] โœ” unique identifier in repo โœ” unique identifier in repo
[[/work/notes]] โœ” valid path from repo root โœ” valid path from repo root
[[work/notes]] โœ” valid path from repo root โœ” valid identifier in repo
[[project/house/todo]] โœ” valid path from repo root โœ” valid unique identifier
[[/project/house/todo]] โœ” valid path from repo root โœ” valid path from repo root
[[house/todo]] โœ” valid unique identifier โœ” valid unique identifier
[[todo]] (ambiguous) โœ˜ ambiguous identifier โœ˜ ambiguous identifier
[[/house/todo]] (wrong path) โœ˜ incorrect path from repo root โœ˜ incorrect path from repo root

User Getting Started First Workspace (`docs/user/getting-started/first-workspace.md`)

Creating Your First Workspace

A Foam workspace is where all your notes, ideas, and knowledge live. Think of it as your digital garden where thoughts can grow and connect. This guide will help you set up a workspace that's organized, scalable, and tailored to your thinking style.

Understanding Workspaces

A Foam workspace is simply a folder containing Markdown files (.md) - your actual notes.

Optionally it can contain:

  • Configuration files - VS Code settings and Foam preferences
  • Assets - images, attachments, and other media
  • Templates - reusable note structures

Single vs. Multiple Workspaces

Recommended: Single Workspace

  • Keep all your knowledge in one place
  • Better link discovery and graph visualization
  • Easier to maintain and backup
  • Follows the "unified knowledge base" principle

Deprecated: Multiple Workspaces (deprecated - advanced users only)

  • Separate professional and personal knowledge
  • Isolate sensitive information
  • Different workflows for different projects

Multiple workspaces are to be considered deprecated at this point, and might become unsupported in the future.
You can simulate a complex workspace by using file/folder links.

The easiest way to start is with our pre-configured template:

Step 1: Create from Template

  1. Visit github.com/foambubble/foam-template
  2. Click "Use this template" (you'll need a GitHub account)
  3. Name your repository (e.g., "john-knowledge-base", "my-second-brain")
  4. Choose visibility:
    • Private - for personal notes (recommended)
    • Public - if you want to share your knowledge openly

Step 2: Clone Locally

bash
git clone https://github.com/yourusername/your-repo-name.git
cd your-repo-name

Step 3: Open in VS Code

  1. Launch VS Code
  2. File > Open Folder
  3. Select your cloned repository folder

Method 2: Start from Scratch

For a minimal setup:

  1. Create a new folder on your computer
  2. Open the folder in VS Code (File > Open Folder)

That's all, you can start working with your markdown files and Foam will take care of the rest.

Already have an Obsidian vault? You can open it directly in VS Code โ€” see [migrating-from-obsidian].

Ideas for your knowledge base

1. Customize Your Settings

Review and adjust .vscode/settings.json based on your preferences:

  • Daily notes location - where your daily notes are stored
  • Image handling - how pasted images are organized
  • Link format - with or without file extensions

2. Set Up Your Inbox

Create inbox.md as your default capture location:

markdown
# Inbox

Quick notes and ideas go here before being organized.

## Today's Captures

-

## To Process

-

## Ideas

-

3. Create Core Structure Notes

Workspace Organization Strategies

Establish your main organizational notes.
You can use any methodology, Foam is not opinionated.

The only recommendation is to get started, you can improve later.

The two main methods adopted by users are PARA and Zettelkasten.

The PARA Method

Organize around four categories:

  • Projects - Things with deadlines
  • Areas - Ongoing responsibilities
  • Resources - Future reference materials
  • Archive - Inactive items

Zettelkasten Approach

Number-based system for atomic ideas:

  • Permanent notes - 202501251030-idea-title.md
  • Literature notes - book-author-year.md
  • Index notes - index-topic.md

4. Configure Daily Notes

Daily notes are perfect for:

  • Daily planning and reflection
  • Meeting notes
  • Journal entries
  • Quick captures

Test your daily notes setup:

  1. Press Ctrl+Shift+P / Cmd+Shift+P
  2. Type "Foam: Open Daily Note"
  3. Verify the note is created in the right location

Alternatively you can press Alt+D to open today's daily note, or Alt+H to open another day's daily note.
Use the .foam/templates/daily-note.md to customize your daily note.

Best Practices for New Workspaces

1. Start Small

  • Begin with just a few notes
  • Don't over-organize initially
  • Let structure emerge naturally

2. Use Templates

  • Create templates for common note types
  • Maintain consistency across similar notes
  • Save time on repetitive formatting
  • Use [[wikilinks]] liberally
  • Don't worry about creating "perfect" links
  • Foam handles broken links gracefully

4. Regular Reviews

  • Weekly workspace cleanup
  • Archive completed projects
  • Identify missing connections

Syncing and Backup

Foam works on simple files, you can add whatever backup method you prefer on top of it.

Git

Your workspace is a Git repository:

bash
git add .
git commit -m "Add new notes and ideas"
git push origin main

You can also use other VS Code extensions to manage the git synching if that's helpful.

Alternative Sync Methods

  • Cloud storage - Dropbox, OneDrive, Google Drive
  • Local backup - Time Machine, File History
  • Manual export - Regular ZIP backups

What's Next?

With your workspace set up, you're ready to:

  1. Learn note-taking fundamentals - Master Markdown and writing effective notes
  2. Explore navigation - Connect your thoughts with wikilinks
  3. Discover the graph view - Visualize your knowledge network
  4. Set up templates - Standardize your note creation process

Getting Help

If you encounter setup issues:


User Getting Started Get Started With Vscode (`docs/user/getting-started/get-started-with-vscode.md`)

Using Foam with VS Code Features

Foam builds on Visual Studio Code's powerful editing capabilities, integrating seamlessly with VS Code's native features to create a comprehensive knowledge management experience. This guide explores how to leverage VS Code's built-in functionality alongside Foam.

Keyboard shortcuts

VS Code supports various keyboard shortcuts, the most important for us are:

Shortcut Action
cmd+N create a new file
cmd+S save the current file
cmd+O open a file
cmd+P use quickpick to open a file
alt+D open the daily note for today
alt+H open the daily note for a day
cmd+shift+P invoke a command (see below)

For more information, see the VS Code keyboard cheat sheets, where you can also see how to customize your keybindings.

Commands

Commands make VS Code extremely powerful.

To invoke a command, press cmd+shift+P and select the command you want to execute.
For example, to see the Foam graph:

  • press cmd+shift+P to open the command bar
  • start typing show graph
  • select the Foam: Show Graph command

And watch the magic unfold.

To see all foam commands, type "foam" in the command bar.
For more information on commands, see commands on the VS Code site.

If you want to learn more about VS Code, check out their website.

Panels

Foam integrates with VS Code panels to provide insights into individual notes and the whole knowledge base.

  • Foam: links: Shows all notes that link to and from the currently active note, helping you understand connections and navigate your knowledge graph
  • Foam: Orphaned Notes: Displays notes that have no incoming or outgoing links, helping you identify isolated content that might need better integration
  • Tag Explorer: Shows all tags used across your workspace in a hierarchical view, see [tags] for more information on tags
  • Foam: Graph: Visual representation of your note connections (also available as a separate graph view)

Styling and Themes

VS Code is very configurable when it comes to themes and style. Find your ideal set up by running the command Color Theme.
For more information see the VS Code documentation.

Multi-Cursor Editing

Edit multiple locations simultaneously for efficient note management:

Basic Multi-Cursor:

  • Alt+Click / Option+Click - Add cursor at click location
  • Ctrl+Alt+Down / Cmd+Option+Down - Add cursor below
  • Ctrl+Alt+Up / Cmd+Option+Up - Add cursor above
  • Ctrl+D / Cmd+D - Select next occurrence of word
  • Ctrl+Shift+L / Cmd+Shift+L - Select all occurrences

Bulk wikilink creation:

  1. Select a word (e.g., "Python")
  2. Press Ctrl+Shift+L to select all occurrences
  3. Type [[]] to wrap all instances
  4. Arrow key to position cursor inside brackets

Find and Replace

Powerful search and replace for note maintenance:

Basic Find/Replace:

  • Ctrl+F / Cmd+F - Find in current file
  • Ctrl+H / Cmd+H - Replace in current file
  • Ctrl+Shift+F / Cmd+Shift+F - Find across workspace
  • Ctrl+Shift+H / Cmd+Shift+H - Replace across workspace

Text Folding

Organize long notes with collapsible sections:

Folding Controls:

  • Click fold icons in the gutter next to headings
  • Ctrl+Shift+[ / Cmd+Option+[ - Fold current section
  • Ctrl+Shift+] / Cmd+Option+] - Unfold current section
  • Ctrl+K Ctrl+0 / Cmd+K Cmd+0 - Fold all
  • Ctrl+K Ctrl+J / Cmd+K Cmd+J - Unfold all

File Management

Explorer Integration

Leverage VS Code's file explorer for note organization:

File Operations:

  • Drag and drop files to reorganize
  • Right-click context menus for quick actions
  • New file/folder creation with shortcuts
  • Bulk selection with Ctrl+Click / Cmd+Click

Quick File Actions:

  • F2 - Rename file (Foam updates links automatically)
  • Delete - Move to trash
  • Ctrl+C / Cmd+C then Ctrl+V / Cmd+V - Copy/paste files
  • Right-click โ†’ Reveal in Explorer/Finder - Open in file system

Quick Open

Rapid file navigation for large knowledge bases:

Quick Open Commands:

  • Ctrl+P / Cmd+P - Go to file
  • Ctrl+Shift+O / Cmd+Shift+O - Go to symbol (headings in Markdown)
  • Ctrl+T / Cmd+T - Go to symbol in workspace
  • Ctrl+G / Cmd+G - Go to line number

Search Patterns:

text
# Go to File (Ctrl+P)
machine       # Finds "machine-learning.md"
proj alpha    # Finds "project-alpha.md"
daily/2025    # Finds files in daily/2025 folder

# Go to Symbol (Ctrl+Shift+O)
@introduction # Jump to "Introduction" heading
@#setup       # Jump to "Setup" heading
:50           # Go to line 50

Search and Discovery

Find content across your entire knowledge base:

Search Interface (Ctrl+Shift+F / Cmd+Shift+F):

  • Search box - Enter your query
  • Replace box - Toggle with replace arrow
  • Include/Exclude patterns - Filter by file types or folders
  • Match case - Case-sensitive search
  • Match whole word - Exact word matching
  • Use regular expression - Advanced pattern matching

Timeline View

Track changes to your notes over time:

Accessing Timeline:

  1. Open Explorer panel
  2. Expand "Timeline" section at bottom
  3. Select a file to see its change history
  4. Click timeline entries to see diff views

Timeline Features:

  • Git commits show when notes were changed
  • File saves track editing sessions
  • Diff views highlight what changed
  • Restore points for recovering previous versions

Outline View

Navigate long notes with hierarchical structure:

Outline Panel:

  1. Enable in Explorer (expand "Outline" section)
  2. Shows heading hierarchy for current note
  3. Click headings to jump to sections
  4. Collapse/expand sections in outline

Version Control Integration

Git Integration

Track changes to your knowledge base:

Source Control Panel:

  • View changes - See modified files
  • Stage changes - Click + to stage files
  • Commit changes - Enter message and commit
  • Sync changes - Push/pull from remote

Git Workflow for Notes:

  1. Write and edit your notes
  2. Review changes in Source Control panel
  3. Stage relevant files for commit
  4. Write meaningful commit message
  5. Commit and push to backup/share

Useful Git Features:

  • Diff view - See exactly what changed
  • File history - Track note evolution over time
  • Branch management - Experiment with different organization approaches
  • Merge conflicts - Resolve when collaborating

Markdown Features

Preview Integration

View formatted notes alongside editing:

Preview Commands:

  • Ctrl+Shift+V / Cmd+Shift+V - Open preview
  • Ctrl+K V / Cmd+K V - Open preview to side
  • Preview lock - Pin preview to specific file

Diagrams (with Mermaid extension):

markdown
```mermaid
graph TD
    A[Foam Workspace] --> B[Notes]
    A --> C[Templates]
    A --> D[Assets]
    B --> E[Wikilinks]
    B --> F[Tags]
    E --> G[Graph View]
```

Extension Ecosystem

Extend Foam's capabilities with complementary extensions.
Look for them in the VS Code Marketplace.

What's Next?

With VS Code mastery, explore advanced Foam topics:

  1. [recommended-extensions] - See complementary extensions to improve your note taking experience
  2. [publish-to-github-pages] - Share your knowledge base

User Getting Started Installation (`docs/user/getting-started/installation.md`)

Installation

Getting started with Foam is straightforward. This guide will walk you through installing everything you need to begin your knowledge management journey.

Step 1: Install Visual Studio Code

Foam is built on VS Code, Microsoft's free, open-source code editor. You can download it at https://code.visualstudio.com/

Why VS Code?

VS Code provides:

  • Excellent Markdown editing capabilities
  • Rich extension ecosystem
  • Cross-platform compatibility
  • Integrated terminal and Git support
  • Customizable interface and shortcuts

To learn more about using VS Code with Foam, check [get-started-with-vscode]

Step 2: Install the Foam Extension

The Foam extension adds knowledge management superpowers to VS Code.

  1. Open VS Code
  2. Click the Extensions icon in the sidebar (or press Ctrl+Shift+X / Cmd+Shift+X)
  3. Search for "Foam" in the extensions marketplace
  4. Click "Install" on the official Foam extension by Foam Team
  5. Reload VS Code when prompted

What the Foam Extension Provides

  • Wikilink auto-completion and navigation
  • Backlink discovery and panel
  • Graph visualization
  • Powerful note template engine
  • Daily notes functionality

While Foam works on its own, it is focused on the networking aspect of your notes. You might want to install additional extensions to improve the editing experience or the functionality of your notes.

Useful Extensions

  • Markdown All in One - Rich Markdown editing features. Highly recommended.

Other extensions:

  • Spell Right - Spell checking for your notes
  • Paste Image - Easily insert images from clipboard
  • Todo Tree - Track TODO items across your workspace

Optional: Install the Foam CLI

The Foam CLI lets you work with your workspace from the terminal โ€” search notes, manage tags, create daily notes, and more โ€” without opening VS Code.

bash
npm install foam-cli

Once installed, run foam --help to see available commands, or see the [[cli|CLI documentation]] for details.

What's Next?

Now that Foam is installed, you're ready to:

  1. [first-workspace] - Set up your knowledge base structure
  2. [get-started-with-vscode] - Learn how to use VS Code for note taking
  3. [note-taking-in-foam] - Write your first Markdown notes
  4. [navigation] - Connect your thoughts with wikilinks
  5. [graph-view] - Visualize your knowledge network

Getting Help

If you encounter issues:


User Getting Started Keyboard Shortcuts (`docs/user/getting-started/keyboard-shortcuts.md`)

Keyboard Shortcuts

Here are some keyboard shortcuts you'll love when editing your notes.

This works best if you can see the result in the preview panel, run the Markdown: Open Preview to the Side command.

You can use either the name or the id to find each shortcut in the settings (File > Preferences > Keyboard Shortcuts) and find out what it is bound to on your system and change it according to your liking.

Shortcut Name ID Extension Use
alt+c - markdown.extension.checkTaskList Markdown All in One Toggle TODO items.
cmd+b - markdown.extension.editing.toggleBold Markdown All in One Make selection bold.
cmd+i - markdown.extension.editing.toggleItalic Markdown All in One Make selection italic.
ctrl+shift+f Format Document editor.action.formatDocument Base Format tables
cmd+shift+f Find files workbench.action.findInFiles Base Search in workspace.
cmd+shift+e Show Explorer workbench.view.explorer Base Show the file explorer.
cmd+alt+v Paste Image extension.pasteImage Paste Image Paste an image from your clipboard.

User Getting Started Navigation (`docs/user/getting-started/navigation.md`)

Navigation is where Foam truly shines. Unlike traditional file systems or notebooks, Foam lets you move through your knowledge by following connections between ideas. This guide will teach you how to navigate efficiently using wikilinks, backlinks, and other powerful features.

[๐Ÿ“น Watch: Mastering navigation in Foam]

Wikilinks are the backbone of Foam navigation. They connect your thoughts and let you jump between related concepts instantly.

markdown
I'm learning about [[Machine Learning]] and its applications in [[Data Science]].

This reminds me of my notes on [[Python Programming]] from yesterday.

When you type [[, Foam shows you a list of existing notes to link to. If the note doesn't exist, Foam creates a placeholder that you can click to create the note later.

Link to a specific heading:

markdown
See the [[Project Management#Risk Assessment]] section for details.

Link to a specific block:

markdown
See the [[Project Management#^block-id]] paragraph for details.

Link with alias:

markdown
According to [[Einstein, Albert|Einstein]], imagination is more important than knowledge.

Foam provides intelligent autocomplete when creating links:

  1. Type [[ - Foam shows a dropdown of existing notes
  2. Start typing - The list filters to matching notes
  3. Use arrow keys to navigate suggestions
  4. Press Enter to insert the selected link

The Foam Graph

For a visual overview of your knowledge base, Foam offers a [graph-view]. This feature renders your notes as nodes and the links between them as connections, creating an interactive map of your thoughts.

[๐Ÿ“น Watch: Navigation with the Foam Graph]

Using the Graph

  1. Open the Command Palette (Ctrl+Shift+P / Cmd+Shift+P)
  2. Run the "Foam: Show Graph" command
  3. The graph will open in a new panel. You can:
  • Click on a node to navigate to that note.
  • Pan and zoom to explore different areas of your knowledge base.
  • See how ideas cluster and identify central concepts.

Backlinks show you which notes reference the current note. This creates a web of knowledge where ideas naturally connect.

  1. Open any note
  2. Look for the "Connections" panel in the sidebar
  3. See all notes that link to your current note
  4. Click any backlink to jump to that note

Quick Navigation Features

Command Palette Navigation

Press Ctrl+Shift+P / Cmd+Shift+P and try these commands:

  • "Foam: Open Random Note" - Discover forgotten knowledge
  • "Foam: Open Daily Note" - Quick access to today's notes
  • "Go to File" (Ctrl+P / Cmd+P) - Fast file opening
  • "Go to Symbol" (Ctrl+Shift+O / Cmd+Shift+O) - Jump to headings within a note

File Explorer Integration

The VS Code file explorer shows your note structure:

  • Click any .md file to open it
  • Use the search box to filter files
  • Right-click for context menus (rename, delete, etc.)

Foam also supports the Note Explorer, which is like the file explorer, but centered around the Foam metadata.

Quick Open

Press Ctrl+P / Cmd+P and start typing:

  • File names - machine finds "machine-learning.md"
  • Partial paths - daily/2025 finds daily notes from 2025
  • Recent files - Empty search shows recently opened files

In Foam broken links are considered placeholders for future notes.
Placeholders (references to non-existent notes) appear differently:

  • In editor: [[missing-note]] will be highlighted a different color
  • In preview: Shows as regular text or with special styling

Clicking on a placeholder in the editor will create the corresponding note.

To find all placeholders:

You can find placeholders by looking at the Placeholders treeview.

Renaming and Moving Notes

When you rename a note file:

  1. Use VS Code's rename function (F2 key)
  2. Foam automatically updates all links to that note
  3. Check the "Problems" panel for any issues

Currently you cannot rename whole folders.

What's Next?

With navigation mastered, you're ready to:

  1. Search and navigate notes - All the ways to find and browse notes
  2. Explore the graph view - Visualize your knowledge network
  3. Learn about backlinks - Master bidirectional linking
  4. Set up templates - Standardize your note creation
  5. Use tags effectively - Add another layer of organization

User Getting Started Note Taking In Foam (`docs/user/getting-started/note-taking-in-foam.md`)

Note-Taking in Foam

Effective note-taking is the foundation of any knowledge management system. In Foam, you'll write notes in Markdown, a simple and powerful format that's both human-readable and widely supported. This guide will teach you everything you need to know about writing great notes in Foam.

Markdown Basics

Markdown is a lightweight markup language that uses simple syntax to format text. Here are the essentials:

Headings

markdown
# Heading 1 (Main Title)

## Heading 2 (Major Section)

### Heading 3 (Subsection)

#### Heading 4 (Minor Section)

Text Formatting

markdown
**Bold text**
_Italic text_
**_Bold and italic_**
~~Strikethrough~~
`Inline code`

Lists

markdown
## Unordered Lists

- First item
- Second item
  - Nested item
  - Another nested item

## Ordered Lists

1. First step
2. Second step
   1. Sub-step
   2. Another sub-step
markdown
[External link](https://example.com)

Code Blocks

markdown
```javascript
function greet(name) {
  return `Hello, ${name}!`;
}
```

Tables

markdown
| Column 1 | Column 2 | Column 3 |
| -------- | -------- | -------- |
| Data 1   | Data 2   | Data 3   |
| Data 4   | Data 5   | Data 6   |

Quotes and Dividers

markdown
> This is a quote or important note
> It can span multiple lines

---

Use three dashes for horizontal dividers

[๐Ÿ“น Watch: Markdown syntax essentials for note-taking]

Foam-Specific Features

Beyond standard Markdown, Foam adds several powerful features:

Connect your notes with double brackets:

markdown
I'm reading about [[Project Management]] and its relationship to [[Personal Productivity]].

This connects to [[2025-01-25-daily-note]] where I first had this insight.

Note Embedding

Include content from other notes via [embeds]:

markdown
![[Project Management#Key Principles]]

This embeds the "Key Principles" section from the Project Management note.

Tags

Organize your content with [tags]:

markdown
#productivity #learning #foam

Tags can be anywhere in your note and help with organization and filtering.

Use nested tags for better organization:

markdown
#work/projects/website
#learning/programming/javascript
#personal/health/exercise

Those tags will show as a tree structure in the Tag Explorer

Note Properties (YAML Front Matter)

Add metadata to your notes:

markdown
---
title: 'Advanced Note-Taking Strategies'
tags: [productivity, learning, methods]
created: 2025-01-25
modified: 2025-01-25
status: draft
---

# Advanced Note-Taking Strategies

Your note content goes here...

Writing Effective Notes

The Atomic Principle

Each note should focus on one concept or idea:

Good Example:

markdown
# The Feynman Technique

A learning method where you explain a concept in simple terms as if teaching it to someone else.

## Steps

1. Choose a topic to learn
2. Explain it in simple terms
3. Identify gaps in understanding
4. Simplify and use analogies

## Why It Works

- Forces active engagement with material
- Reveals knowledge gaps quickly
- Improves retention through teaching

Related: [[Active Learning]] [[Study Methods]]

Avoid:
Mixing multiple unrelated concepts in one note.

Use Descriptive Titles

Your note titles should clearly indicate the content:

Good: REST API Design Principles
Good: Meeting Notes - Product Roadmap Review 2025-01-25
Avoid: Stuff I Learned Today
Avoid: Notes

Don't hesitate to create links, even to notes that don't exist yet:

markdown
# Machine Learning Fundamentals

Machine learning is a subset of [[Artificial Intelligence]] that focuses on creating algorithms that can learn from [[Data]].

Key concepts include:

- [[Supervised Learning]]
- [[Unsupervised Learning]]
- [[Neural Networks]]
- [[Feature Engineering]]

This connects to my work on [[Customer Behavior Analysis]] and [[Predictive Analytics]].

Foam will create placeholder pages for missing notes, making it easy to fill in knowledge gaps later.

Keyboard Shortcuts

Essential VS Code shortcuts for note-taking:

Shortcut Action
Ctrl+N / Cmd+N New file
Ctrl+S / Cmd+S Save file
Ctrl+P / Cmd+P Quick file open
Ctrl+Shift+P / Cmd+Shift+P Command palette
Ctrl+K V / Cmd+K V Open Markdown preview
Ctrl+[ / Cmd+[ Decrease indent
Ctrl+] / Cmd+] Increase indent
Alt+Z / Option+Z Toggle word wrap

What's Next?

Now that you understand note-taking basics:

  1. [navigation] - Learn to move efficiently between notes with wikilinks
  2. Explore the graph view - Visualize the connections in your knowledge base
  3. Set up templates - Create reusable note structures
  4. Use daily notes - Establish a daily capture routine

These extensions defined in .vscode/extensions.json are automatically installed when you accept the workspace's recommended extensions.

This list is subject to change.

Extensions For Additional Features

These extensions are not defined in .vscode/extensions.json, but have been used by others and shown to play nice with Foam.

  • Emojisense (provides emoji autocomplete and suggestions in markdown files)
  • Markdown Emoji (adds :smile: syntax support, works with emojisense to provide autocomplete for this syntax)
  • Mermaid diagrams Support (adds syntax highlighting for Mermaid code blocks in markdown and renders Mermaid diagrams in markdown preview)
  • Excalidraw whiteboard and sketching tool integration (create and edit hand-drawn style diagrams and sketches directly in VS Code)
  • VSCode PDF Viewing (view PDF files directly within VS Code without external applications)
  • Project Manager (easily switch between multiple projects and workspaces)
  • Markdown Extended (extended markdown syntax support with additional formatting options - use with kbd option disabled as it conflicts with wikilinks)
  • GitDoc (automatic git commits for easy version management and backup of your notes)
  • Todo Tree (scans workspace for TODO, FIXME, and other comment tags, displaying them in a tree view and editor gutter)

User Getting Started Sync Notes (`docs/user/getting-started/sync-notes.md`)

Sync notes with source control

Source control is a way to precisely manage the history and content of a directory of files.
Often used for program code, this feature is very useful for note taking as well.

There are (too) many ways to commit your changes to source control:

  • Using VS Code's own git integration
    • The quick and easy way is to use the Git: Commit All command after editing files. The default Foam workspace settings will stage & sync all of your changes to the remote:
  • Using GitDoc for [automatic-git-syncing]
  • Whatever way you like to do it (CLI?)

User Publishing Generate Gatsby Site (`docs/user/publishing/generate-gatsby-site.md`)

Generate a site using Gatsby

Using foam-gatsby-template

You can use foam-gatsby-template to generate a static site to host it online on GitHub or Vercel.

Publishing your foam to GitHub pages

It comes configured with GitHub actions to auto deploy to GitHub pages when changes are pushed to your main branch.

Publishing your foam to Vercel

When you're ready to publish, run a local build.

bash
cd _layouts
npm run build

Remove public from your .gitignore file then commit and push your public folder in _layouts to GitHub.

Log into your Vercel account. (Create one if you don't have it already.)

Import your project. Select _layouts/public as your root directory and click Continue. Then name your project and click Deploy.

That's it!

Using foam-template-gatsby-kb

You can use another template foam-template-gatsby-kb, and host it on Vercel or Netlify.

Using foam-template-gatsby-theme-primer-wiki

You can use another template foam-template-gatsby-theme-primer-wiki, (Demo), and host it on Github Pages, Vercel or Netlify.


User Publishing Math Support With Katex (`docs/user/publishing/math-support-with-katex.md`)

Katex Math Rendering

Apart from using the method mentioned in [math-support-with-mathjax], we can also use KaTeX to render our math equations in Foam. The caveat is: we can't rely on GitHub Pages to host and deploy our website anymore, because the plugin we'll be using to let Jekyll support KaTeX doesn't play well together with GitHub Pages.

The alternative solution is to using [publish-to-vercel] for building and publishing our website, so before you start integrating KaTeX into your Foam project, please follow the instructions to host your Foam workspace on [publish-to-vercel] first.

Adding required plugins

Add the plugin jekyll-katex to your Foam workspace's _config.yml and Gemfile if you haven't done so already. For detailed instructions, please refer to the #Adding a _config.yml and #Adding a Gemfile in [publish-to-vercel].

Loading KaTeX JS and CSS

Because we are using KaTeX to render math, we will also need to import KaTeX's JS and CSS files from CDN. The official method to load these files is documented at: KaTeX/KaTeX#starter-template. In our case, we will need to add the following code snippet to our _layouts/page.html:

html
--- layout: default ---

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
  integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X"
  crossorigin="anonymous"
/>


<script
  defer
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"
  integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4"
  crossorigin="anonymous"
></script>


<script
  defer
  src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"
  integrity="sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa"
  crossorigin="anonymous"
  onload="renderMathInElement(document.body);"
></script>

Adding liquid tags to wrap page content

The plugin jekyll-katex focuses on rendering:

  • Single math equations wrapped inside katex liquid tags like {% raw %}{% katex %} ... {% endkatex %}{% endraw %}.
  • Or multiple math equations in paragraphs wrapped inside {% raw %}{% katexmm %} ... {% endkatexmm %}{% endraw %}.

In our case, we'll be using the latter tag to wrap our {% raw %}{{ content }}{% endraw %}. Wrap {% raw %}{{ content }}{% endraw %} in the liquid tags inside _layouts/page.html like so:

html
{% raw %}{% katexmm %} {{ content }} {% endkatexmm %}{% endraw %}

Render equations in Foam's homepage as well

You may have noticed that we only made modifications to the template _layouts/page.html, which means that _layouts/home.html won't have KaTeX support. If you wan't to render math in Foam's home page, you'll need to make the same modifications to _layouts/home.html as well.

Finally, if all goes well, then our site hosted on Vercel will support rendering math equations with KaTeX after committing these changes to GitHub. Here's a demo of the default template with KaTeX support: Foam Template with KaTeX support.


User Publishing Math Support With Mathjax (`docs/user/publishing/math-support-with-mathjax.md`)


layout: mathjax

Math Support

Published Foam pages don't support math formulas by default. To enable this feature, you can add the following code snippet to the end of _layouts/page.html:

html
<script
  src="https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
  type="text/javascript"
></script>
<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
          inlineMath: [['$','$']]
      }
  });
</script>

This approach uses the MathJax library to render anything delimited by $ (customizable in the snippet above) pairs to inline math and $$ to blocks of math (like a html div tag) using with the AMS-LaTeX dialect embedded within MathJax.

Example of inline math using $...$:

$e^{i \pi}+1=0$, becomes $e^{i \pi}+1=0$

Example of a math block using $$...$$:

$$ f_{\mathbf{X}}\left(x_{1}, \ldots, x_{k}\right)=\frac{\exp \left(-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^{\mathrm{T}} \mathbf{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu})\right)}{\sqrt{(2 \pi)^{k}|\mathbf{\Sigma}|}} $$

Becomes:

$$ f*{\mathbf{X}}\left(x*{1}, \ldots, x_{k}\right)=\frac{\exp \left(-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^{\mathrm{T}} \mathbf{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu})\right)}{\sqrt{(2 \pi)^{k}|\mathbf{\Sigma}|}} $$

Alternative approaches

There are other dialects of LaTeX (instead of AMS), and other JavaScript rendering libraries you may want to use. In a future version of Foam, we may support KaTeX syntax out of the box, but at this time, these integrations are left as an exercise to the user.

Why don't my Math expressions work on my Foam's home page?

If you want the index page of your Foam site to render maths, you'll need to add that to _layouts/home.html as well, or change the layout of the index page to be "page" instead of "home" by putting this Front Matter on the top of your readme.md/index.md:

text
---
layout: page
---

# Your normal title here

Reference: How to support latex in github-pages


User Publishing Publish To Azure Devops Wiki (`docs/user/publishing/publish-to-azure-devops-wiki.md`)

Publish to Azure DevOps Wiki

Publish your Foam workspace as an Azure DevOps wiki.

Azure DevOps is Microsoft's collaboration software for software development teams, formerly known as Team Foundation Server (TFS) and Visual Studio Team Services. It is available as an on-premise or SaaS version. The following recipe was tested with the SaaS version, but should work the same way for the on-premise.

The following recipe is written with the assumption that you already have an Azure DevOps project.

Setup a Foam workspace

  1. Generate a Foam workspace using the foam-template project.
  2. Change the remote to a git repository in Azure DevOps (Repos -> Import a Repository -> Add Clone URL with Authentication), or copy all the files into a new Azure DevOps git repository.
  3. Define which document will be the wiki home page. To do that, create a file called .order in the Foam workspace root folder, with first line being the document filename without .md extension. For a project created from the Foam template, the file would look like this:
text
readme
  1. Push the repository to remote in Azure DevOps.

Publish repository to a wiki

  1. Navigate to your Azure DevOps project in a web browser.
  2. Choose Overview > Wiki. If you don't have wikis for your project, choose Publish code as a wiki on welcome page.
  3. Choose repository with your Foam workspace, branch (usually master or main), folder (for workspace created from foam-template it is /), and wiki name, and press Publish.

A published workspace looks like this:

There is default table of contents pane to the left of the wiki content. Here, you'll find a list of all directories that are present in your Foam workspace, and all wiki pages. Page names are derived from files names, and they are listed in alphabetical order. You may reorder pages by adding filenames without .md extension to .order file.

Note that first entry in .order file defines wiki's home page.

Update wiki

While you are pushing changes to GitHub, you won't see the wiki updated if you don't add Azure as a remote. You can push to multiple repositories simultaneously.

  1. First open a terminal and check if Azure is added running: git remote show origin. If you don't see Azure add it in the output then follow these steps.
  2. Rename your current remote (most likely named origin) to a different name by running: git remote rename origin main
  3. You can then add the remote for your second remote repository, in this case, Azure. e.g git remote add azure https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes. You can get it from: Repos->Files->Clone and copy the URL.
  4. Now, you need to set up your origin remote to push to both of these. So run: git config -e and edit it.
  5. Add the remote origin section to the bottom of the file with the URLs from each remote repository you'd like to push to. You'll see something like that:
bash
[core]
 ...
  (ignore this part)
  ...
[branch "main"]
 remote = github
 merge = refs/heads/main
[remote "github"]
 url = [email protected]:username/repo.git
 fetch = +refs/heads/*:refs/remotes/github/*
[remote "azure"]
 url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
 fetch = +refs/heads/*:refs/remotes/azure/*
[remote "origin"]
 url = [email protected]:username/repo.git
 url = https://<YOUR_ID>@dev.azure.com/<YOUR_ID>/foam-notes/_git/foam-notes
  1. You can then push to both repositories by: git push origin main or a single one using: git push github main or git push azure main

For more information, read the Azure DevOps documentation.


User Publishing Publish To Github Pages (`docs/user/publishing/publish-to-github-pages.md`)

GitHub Pages

  1. In VSCode workspace settings set "foam.edit.linkReferenceDefinitions": "withoutExtensions"
  2. Execute the โ€œFoam: Lint Workspaceโ€ command from the command palette.
  3. Turn GitHub Pages on in your repository settings.
    • The default GitHub Pages template is called Primer. See Primer docs for how to customise html layouts and templates.
    • GitHub Pages is built on Jekyll, so it supports things like permalinks, front matter metadata etc.

How to publish locally

If you want to test your published foam, follow the instructions:

Assuming you have installed ruby/jekyll and the rest:

  • touch Gemfile
    • open the file and paste the following:
text
source 'https://rubygems.org'

gem "github-pages", "VERSION"

replacing VERSION with the latest from https://rubygems.org/gems/github-pages (e.g. gem "github-pages", "209")

  • bundle
  • bundle exec jekyll 3.9.0 new .
  • edit the Gemfile according to the instructions at Creating Your Site Point n.8
  • bundle exec jekyll serve

Other templates

There are many other templates which also support publish your foam workspace to github pages


User Publishing Publish To Github (`docs/user/publishing/publish-to-github.md`)

Publish to GitHub

The standard foam-template is ready to be published to GitHub, and GitHub pages.

Enable navigation in GitHub

To allow navigation from within the GitHub repo, make sure to generate the link references, by setting

  • Foam โ€บ Edit: Link Reference Definitions -> withExtensions

See [link-reference-definitions] for more information.

Customising the style

You can edit assets/css/style.scss to change how published pages look.


User Publishing Publish To Gitlab Pages (`docs/user/publishing/publish-to-gitlab-pages.md`)

GitLab Pages

You don't have to use GitHub to serve Foam pages. You can also use GitLab.

Gitlab pages can be kept private for private repo, so that your notes are still private.

Setup a project

Generate the directory from GitHub

Generate a solution using the Foam template.

Change the remote to GitLab, or copy all the files into a new GitLab repo

Publishing pages with Gatsby

Setup the Gatsby config

Add a .gatsby-config.js file where:

  • $REPO_NAME correspond to the name of your gtlab repo.
  • $USER_NAME correspond to your gitlab username.
text
/* Detailed source-code truncated for AI context efficiency. */

And a package.json file containing:

json
{
  "private": true,
  "name": "wiki",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "develop": "gatsby develop -H 0.0.0.0",
    "start": "gatsby develop -H 0.0.0.0",
    "build": "gatsby build",
    "clean": "gatsby clean",
    "serve": "gatsby serve",
    "test": "echo test"
  },
  "dependencies": {
    "@primer/react": "^34.1.0",
    "@primer/css": "^17.5.0",
    "foam-cli": "^0.11.0",
    "gatsby": "^3.12.0",
    "gatsby-plugin-manifest": "^3.12.0",
    "gatsby-plugin-robots-txt": "^1.6.9",
    "gatsby-plugin-sitemap": "^5.4.0",
    "gatsby-source-filesystem": "^3.12.0",
    "gatsby-theme-primer-wiki": "^1.14.5",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}

The theme will be based on gatsby-theme-primer-wiki.

To test the theme locally first run yarn install and then use gatsby develop to serve the website.
See gatsby documentation for more details.

Set-up the CI for deployment

Create a .gitlab-ci.yml file containing:

yml
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml

image: node:latest

stages:
  - deploy

pages:
  stage: deploy
  # This folder is cached between builds
  # https://docs.gitlab.com/ee/ci/yaml/index.html#cache
  cache:
    paths:
      - node_modules/
      # Enables git-lab CI caching. Both .cache and public must be cached, otherwise builds will fail.
      - .cache/
      - public/
  script:
    - yarn install
    - ./node_modules/.bin/gatsby build --prefix-paths
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

This pipeline will now serve your website on every push to the main branch of your project.

Publish with Jekyll

Add a \_config.yaml

Add another file to the root directory (the one with readme.md in it) called _config.yaml (no extension)

yaml
title: My Awesome Foam Project
baseurl: '' # the subpath of your site, e.g. /blog
url: '/' # the base hostname & protocol for your site
theme: jekyll-theme-minimal
plugins:
  - jekyll-optional-front-matter
optional_front_matter:
  remove_originals: true
defaults:
  - scope:
      path: '' # we need to add this to properly render layouts
    values:
      layout: 'default'

You can choose a theme if you want from places like Jekyll Themes

Add a Gemlock file

Add another file to the root directory (the one with readme.md in it) called Gemfile (no extension)

ruby
source "https://rubygems.org"

gem "jekyll"
gem "jekyll-theme-minimal"
gem "jekyll-optional-front-matter"

Commit the file and push it to gitlab.

Setup CI/CD

  1. From the project home in GitLab click Set up CI/CD
  2. Choose Jekyll as your template from the template dropdown
  3. Click commit
  4. Now when you go to CI / CD > Pipelines, you should see the code running

Troubleshooting

  • Could not locate Gemfile - You didn't follow the steps above to Add a Gemlock file
  • Conversion error: Jekyll::Converters::Scss encountered an error while converting You need to reference a theme.
  • Pages are running in CI/CD, but I only ever see test, and never deploy - Perhaps you've renamed the main branch (from master) - check the settings in .gitlab-ci.yml and ensure the deploy command is running to the branch you expect it to.
  • I deployed, but my .msd files don't seem to be being converted into .html files - You need a gem that GitHub installs by default - check gem "jekyll-optional-front-matter" appears in the Gemfile

User Publishing Publish To Netlify With Eleventy (`docs/user/publishing/publish-to-netlify-with-eleventy.md`)

Publish to Netlify with Eleventy

You can use foam-eleventy-template to generate a static site with Eleventy, and host it online on Netlify.

With this template you can

  • Have control over what to publish and what to keep private
  • Customize the styling of the site to your own liking

Publishing your foam

When you're ready to publish, import the GitHub repository you created with foam-eleventy-template into your Netlify account. (Create one if you don't have it already.)

Once that's done, all you have to do is make changes to your workspace in VS Code and push them to the main branch on GitHub. Netlify will recognize the changes, deploy them automatically and give you a link where your Foam is published.

That's it!

You can now see it online and use that link to share it with your friends, so that they can see it too.


User Publishing Publish To Vercel (`docs/user/publishing/publish-to-vercel.md`)

Publish to Vercel

This #recipe shows you how to deploy the default Foam website template to Vercel.

Vercel is a static website hosting solution similar to GitHub Pages (see [publish-to-github-pages]).

Setting up the project

Using Foam's template

Generate a GitHub repository using the default Foam template, this will be the workspace that we will be deploying with Vercel. This workspace is a barebone Jekyll source website, which means we can customize and install plugins just like any other Jekyll websites.

As we won't be using GitHub Pages, we will be adding a few configuration files in order to help Vercel pick up on how to build our site.

Adding a `_config.yml`

First, we'll need to add a _config.yml at the root directory. This is the Jekyll configuration file. In here, we will set the site's title, theme, repository and permalink options, and also tell Jekyll what plugins to use:

yaml
# _config.yml
title: Foam
# All the plugins we will be installing now that we won't be using GitHub Pages
plugins:
  - jekyll-katex # optional
  - jekyll-default-layout
  - jekyll-relative-links
  - jekyll-readme-index
  - jekyll-titles-from-headings
  - jekyll-optional-front-matter
# The default Jekyll theme we will be using
theme: jekyll-theme-primer
# The GitHub repository that we are hosting our foam workspace from
repository: user/repo
# Generate permalinks in format specified in: https://jekyllrb.com/docs/permalinks/#built-in-formats
permalink: pretty

The theme specifies a theme for our deployed Jekyll website. The default GitHub Pages template is called Primer. See Primer docs for how to customise html layouts and templates. We can also choose a theme if you want from places like Jekyll Themes.

The plugins specifies a list of Jekyll plugins that we will be installing in the next section. As we won't be using GitHub Pages, we'll need to install these plugins that GitHub Pages installs for us under the hood.

If you want to use LaTeX rendered with KaTeX (which is what the plugin jekyll-katex does), you can specify it here. And yes, one of the benefits of deploying with Vercel is that we can use KaTeX to render LaTeX! More on: [math-support-with-katex]

Adding a `Gemfile`

Next up, we'll create another new file called Gemfile in the root directory. This is where we will let Vercel know what plugins to install when building our website.

In our Gemfile, we need to specify our Ruby packages:

ruby
# Gemfile
source "https://rubygems.org"
gem "jekyll"
gem "kramdown-parser-gfm"
gem "jekyll-theme-primer"
gem "jekyll-optional-front-matter"
gem "jekyll-default-layout"
gem "jekyll-relative-links"
gem "jekyll-readme-index"
gem "jekyll-titles-from-headings"
gem "jekyll-katex"  # Optional, the package that enables KaTeX math rendering

Enable math rendering with KaTeX (optional)

Besides adding the plugin jekyll-katex in _config.yml and Gemfile, we'll also have to follow the guides in [math-support-with-katex] to let our site fully support using KaTeX to render math equations.

Committing changes to GitHub repo

Finally, commit the newly created files to GitHub.

Importing project to Vercel

First, import our foam workspace (GitHub repository) to Vercel with Vercel's Import Git Repository. Paste our GitHub repo's url and Vercel will automatically pull and analyze the tool we use to deploy our website. (In our case: Jekyll.)

Next, select the folder to deploy from if prompted. If we are using the default template, then Vercel will default to the root directory of our Foam workspace.

Finally, if all is successful, Vercel will show the detected framework: Jekyll. Press Deploy to proceed on publishing our project.

And now, Vercel will take care of building and rendering our foam workspace each time on push. Vercel will publish our site to xxx.vercel.app, we can also define a custom domain name for our Vercel website.


User Recipes Add Images To Notes (`docs/user/recipes/add-images-to-notes.md`)

Add images to your notes

This #recipe allows you to paste images on to your notes.

VScode (since
1.79)
now has the ability to paste images from the clipboard, or drag-and-drop image
files, directly into markdown documents. The file will be created in the
workspace, and a link generated in Markdown format.

VSCode settings under Markdown > Copy Files and Markdown > Editor > Drop can
be used to configure where the files get placed in your workspace, how they're
named, how conflicts with existing files are handled, and more.


User Recipes Automatic Git Syncing (`docs/user/recipes/automatic-git-syncing.md`)

Automatically Sync with Git

With this #recipe you can regularly commit and push to git, to keep your repo in always synched.
You can also easily manipulate the git history to reduce clutter.

Required Extensions

Instructions

Click on the extension link above to see how to use it.

For Foam specific needs, you can add a comment here by following the contribution guide.

Feedback and issues

  • Feedback and issues with the extension should be reported to the authors themselves
  • Feedback and issues with the integration of the extension in Foam can be reported in our issue tracker

With this #recipe you can convert a link to a fully-formed Markdown link, using the page's title as a display name. Useful for citations and creating link collections.

Required Extensions

Markdown Link Expander will scrape your URL's <title> tag to create a nice Markdown-style link.

Instructions

  1. Highlight desired URL
  2. Cmd + Shift + P
  3. Expand URL to Markdown
  4. Profit

Tip: If you paste a lot of links, give the action a custom key binding

Feedback and issues

Have an idea for the extension? Feel free to share! ๐ŸŽ‰


User Recipes Capture Notes With Drafts Pro (`docs/user/recipes/capture-notes-with-drafts-pro.md`)

Capture Notes With Drafts Pro

With this #recipe you can create notes on your iOS device, which will automatically be imported into Foam.

Context

Other tools

  • We assume you are familiar with how to use GitHub (if you are using Foam this is implicit)
  • You have an iOS device with Drafts
  • You have upgraded to Drafts Pro (needed to edit actions).

Instructions

  1. Create a new action in Drafts
  2. Add a single step of type Script
  3. Edit the script adding the code from the block below
  4. Edit settings at the top of the script to suit your preferences
  5. Set other Action options in Drafts as you wish
  6. Save the Action
  7. In GitHub create a Personal Access Token and give it repo scope - make a note of the token
  8. In Drafts create a note
  9. Select the action you created in steps 1-6
  10. On the first run you will need to add the following information:
    1. your GitHub username
    2. the repository name of your Foam repo
    3. the GitHub access token from step 7
    4. An author name
  11. Check your GitHub repo for a commit
  12. If you are publishing your Foam to the web you may want to edit your publishing configuration to exclude inbox files - as publishing (and method) is a user choice that is beyond the scope of this recipe

Code for Drafts Action

text
/* Detailed source-code truncated for AI context efficiency. */

User Recipes Capture Notes With Shortcuts And Github Actions (`docs/user/recipes/capture-notes-with-shortcuts-and-github-actions.md`)

Capture Notes With Shortcuts and GitHub Actions

With this #recipe you can create notes on your iOS device, which will automatically be imported into Foam.

Context

Other tools

  • We assume you are familiar with how to use GitHub (if you are using Foam this is implicit)
  • You have an iOS device.

Instructions

  1. Setup the foam-capture-action in your GitHub Repository, to be triggered by "Workflow dispatch" events.
text
name: Manually triggered workflow
on:
  workflow_dispatch:
    inputs:
      data:
        description: 'What information to put in the knowledge base.'
        required: true

jobs:
  store_data:
    runs-on: ubuntu-latest
    # If you encounter a 403 error from a workflow run, try uncommenting the following 2 lines (taken from: https://stackoverflow.com/questions/75880266/cant-make-push-on-a-repo-with-github-actions accepted answer)
    # permissions:
          # contents: write
    steps:
    - uses: actions/checkout@master
    - uses: anglinb/foam-capture-action@main
      with:
        {% raw %}
        capture: ${{ github.event.inputs.data }}
        {% endraw %}
    - run: |
        git config --local user.email "[email protected]"
        git config --local user.name "Your name"
        git commit -m "Captured from workflow trigger" -a
        git push -u origin master
  1. In GitHub create a Personal Access Token and give it repo scope - make a note of the token
  2. Run this command to find your workflow-id to be used in the Shortcut.
bash
curl \
  -H "Accept: application/vnd.github.v3+json" \
  -H "Authorization: Bearer <GITHUB_TOKEN>" \
    https://api.github.com/repos/<owner>/<repository>/actions/workflows
  1. Copy this Shortcut to your iOS devices and edit the contents of the last step, GetContentsOfURL

    • Make sure you update the URL of the shortcut step with the owner, repository, workflow-id (from the previous step)
    • Make sure you update the headers of the shortcut step, replaceing [GITHUB_TOKEN] with your Personal Access Token (from step 2)
  2. Run the shortcut & celebrate! โœจ (You should see a GitHub Action run start and the text you entered show up in inbox.md in your repository.)


User Recipes Diagrams In Markdown (`docs/user/recipes/diagrams-in-markdown.md`)

Diagrams in Markdown

We have two alternative #recipe for displaying diagrams in markdown:

Mermaid

You can use Mermaid plugin to draw and preview diagrams in your content.

Draw.io

Draw.io extension allows you to create, edit, and display your diagrams without leaving Visual Studio Code. The .drawio.svg or .drawio.png files can be automatically embedded and displayed in published Foams, no export needed. FYI, the diagram below was made using Draw.io! You can check the diagram here.

Using Draw.io

  1. Install Draw.io VS Code extension.
  2. Create a new *.drawio.svg or *.drawio.png file.
  3. Start drawing your diagram. Once you done, save it.
  4. Embed the diagram file as you embedding the image file, for example: ``

User Recipes Export To Pdf (`docs/user/recipes/export-to-pdf.md`)

Export to PDF

This #recipe shows how to export a note to PDF.

Required extensions

Required third-party tools

Check that Pandoc is installed by opening a terminal and running pandoc --version.

Check that Pandoc can produce PDFs with LaTeX by running the following in the terminal.

text
echo It is working > test.md
pandoc test.md -o test.pdf

Instructions

  1. Create a folder in your workspace named .pandoc. Take note of the full path to this directory. The rest of this recipe will refer to this path as $WORKSPACE/.pandoc.

  2. Download the template file foam.latex from Hegghammer/foam-templates and place it in $WORKSPACE/.pandoc.

  3. In VSCode, open settings.json for your user (or just for your workspace if you prefer), and add the following line:

text
"pandoc.pdfOptString": "--from=markdown+wikilinks_title_after_pipe --resource-path $WORKSPACE/.pandoc --template foam --listings",

Make sure to replace $WORKSPACE/.pandoc with the real full path to the .pandoc directory you created earlier.

  1. Open a Foam note in VSCode.

  2. Press Ctrl + k, p. Choose "pdf", and press Enter.

The PDF should look something like this:

Options

If you include a name in the author parameter in the YAML of the Foam note, that name will feature in the PDF header on the top left.

If you don't want syntax highlighting and frames around the codeblocks, remove --listings from the pandoc.pdfOptString parameter in settings.json.

Further customization

If you know some LaTeX, you can tweak the foam.latex template to your needs. Alternatively, you can supply another ready-made template such as Eisvogel; just place the TEMPLATE_NAME.latex file in $WORKSPACE/.pandoc. You can also use all of Pandoc's other functionalities by tweaking the pandoc.pdfOptString parameter in settings.json.


User Recipes Generate Material For Mkdocs Site (`docs/user/recipes/generate-material-for-mkdocs-site.md`)

Generate a site using the Material for MkDocs theme

Configuring a static-site generator (SSG) to publish your Foam provides access to functionality not available through Foam's default publishing mechanism. For example, compare the original Foam documentation site with a Material for MkDocs version created using the simple configuration detailed below. Try out the search functionality on the Material for MkDocs version. This digital garden and this blog provide more advanced examples of Foam content published using Material for MkDocs.

The following explains how to configure the Material for MkDocs theme for the MkDocs SSG to publish your Foam.

Like most SSGs (e.g. Gatsby is another SSG that can be used to publish your Foam) site content is accepted in the form of Markdown files. Like those produced by Foam. SSGs differ in the languages they are written in (MkDocs is Python, Gatsby is Javascript and React) and the features they provide. MkDocs and Material for MkDocs are designed to support project documentation. Gatsby is more general purpose and provides a nice feature set.

You choose your poison.

Requirements

To use Material for MkDocs to publish your Foam you need:

Instructions

Configuring Material for MkDocs to publish your Foam involves the following steps:

  1. Install Material for MkDocs and other requirements.

    Install the Material for MkDocs theme, MkDocs, and other required Python modules.

  2. Configure Material for MkDocs for your Foam.

    Create a mkdocs.yml file in the root of your Foam workspace directory. This file configures Material for MkDocs to work with your Foam.

  3. Preview and test your site locally.

    Run MkDocs to preview and test your Material for MkDocs Foam site locally. Good for testing and local use.

  4. Further customise Material for MkDocs.

    Explore and leverage the additional configuration settings, possible customisations, and additional themes and plugins to customise your site to your needs.

  5. Publish your site.

    Publish your Material for MkDocs Foam site to the web for others to enjoy. There are many options for publishing your site, including GitHub, GitLab, Netlify, and others.

Install Material for MkDocs and other requirements

Material for MkDocs provides detailed installation instructions which cover the full range of options for installing and configuring Material for MkDocs. The following is a summary of the recommended process.

  1. Within your Foam workspace directory, create a Python virtual environment

    • python -m venv .venv
    • source .venv/bin/activate (Linux/Mac) or .venv\Scripts\activate (Windows)
  2. Install Material for MkDocs

    • pip install mkdocs-material
  3. Install additional Python modules

    • pip install mkdocs-roamlinks-plugin
    • pip install mkdocs-exclude

Configure Material for MkDocs for your Foam

To configure Material for MkDocs for your Foam workspace, create a mkdocs.yml file in the root of your Foam workspace directory. Below you will find a sample mkdocs.yml file (adapted from the foam-mkdocs-template repository). Copy and paste it into your mkdocs.yml file, then edit it to suit your needs. In particular, don't forget to change the site_name and site_url to match your Foam workspace. Though this can be left a little later.

Material for MkDocs provides documentation on both minimal and advanced configuration of mkdocs.yml. Which are revisited in the customise section below

yaml
site_name: My site # Change this to your site name
site_url: https://mydomain.org/mysite # change this
theme:
  name: material
  features:
    - navigation.expand
    - tabs
markdown_extensions:
  - attr_list
  - pymdownx.tabbed
  - nl2br
  - toc:
      permalink: '#'
      slugify: !!python/name:pymdownx.slugs.uslugify
  - admonition
  - codehilite:
      guess_lang: false
      linenums: false
  - footnotes
  - meta
  - def_list
  - pymdownx.arithmatex
  - pymdownx.betterem:
      smart_enable: all
  - pymdownx.caret
  - pymdownx.critic
  - pymdownx.details
  - pymdownx.inlinehilite
  - pymdownx.magiclink
  - pymdownx.mark
  - pymdownx.smartsymbols
  - pymdownx.superfences
  - pymdownx.tasklist
  - pymdownx.tilde
plugins:
  - search
  - roamlinks
  - exclude:
      glob:
        - '*.tmp'
        - '*.pdf'
        - '*.gz'
      regex:
        - '.*\.(tmp|bin|tar)$'

Preview and test your site locally

MkDocs provides a live preview server allowing you to preview and test your Material for MkDocs Foam site. The server will continue to rebuid your site as you write.

The simplest method to use the preview service is to run the following command whilst in the rood directory of your Foam workspace:

bash
mkdocs serve

See the Material for MkDocs site for more, including how to run the preview server via docker

Further customise your site

Further customisation is available through expanding the configuration of Material for MkDocs, using additional MkDocs plugins, customising HTML/CSS, using Markdown extensions, writing your own Python scripts, and more.

For more on the available customisation paths, see the following:

  • Material for MkDocs Advanced configuration or the Set up section

    For more configuration options to be included in your mkdocs.yml file, including customising: colours, fonts, language, icons, navigation, header, footer etc.

  • Material for MkDocs Customisation

    For advice on enhancing the visual design of your site by customising and replacing provided HTML, CSS, and Javascript.

  • Material for MkDocs Reference

    An overview of customisation methods that can be used directly within your Markdown files, including: admonitions, annotations, buttons, code blocks, content tabs, data tables, diagrams, grids, Mathematics, etc.

  • a catalog of 300 MkDocs projects and plugins

    For functionality and ideas not included in Material for MkDocs, including: additional themes, plugins, and extensions.

Building and publishing your site

As a Static Site Generator (SSG), MkDocs generates a collection of static HTML and other types of files. Publishing your site involves building those HTML files and placing them onto your web server. The method will vary depending on your web server and hosting provider.

The MkDocs documentation site provides an explanation of the simplest method to publish your site to any provider using mkdocs build and scp.

The Material for MkDocs publish page lists options for publishing to


User Recipes How To Write Recipes (`docs/user/recipes/how-to-write-recipes.md`)

How to Write Recipes

This is an example of how to structure a Recipe. The first paragraph or two should explain the purpose of the recipe succinctly, including why it's useful, if that's not obvious.

Recipes are intended to document:

  • How to use Foam's basic features
  • Power user pro-tips
  • Useful customisations of the default Foam environment
  • Integrations with third party tools and extensions (should be listed below)

Required Extensions

The first section should be a bulleted list of extensions required to use this recipe. At a minimum, this section should list all additional, non-standard extensions.

Ideally, you should also note which Foam [recommended-extensions] are responsible for providing this feature, so any issue reports can be directed to the correct repositories.

When creating new recipes, if you don't know which extension does what, you can leave it out.

Instructions

Here we describe how the extension should be used.

You may include a screenshot or GIF of the feature in action by uploading an image to the assets/images directory. Please try to keep GIFs as small as possible by recording them with a low frame rate.

That's pretty much it!

How to contribute

You can add [recipes] by creating a pull request to foambubble/foam on GitHub.

Read more in our contribution guide.


User Recipes Markup Converter (`docs/user/recipes/markup-converter.md`)

Markup Converter

This #recipe allows you to convert any document into Markdown for storing them in your notes.

We will be using Pandoc, a popular universal document converter. It can convert documents in Microsoft Word, HTML, LaTeX, and many other formats to various formats including markdown and many others.

Instructions

We will go through the example of converting Microsoft Word documents to Markdown. For detailed instructions on how to use Pandoc, please refer to the Pandoc documentation.

  1. Install Pandoc
  2. Open the terminal of your choice and verify that Pandoc is installed by running pandoc --version
  3. Copy the Microsoft Word documents that you want to convert into a new folder
  4. Change the current directory to the folder containing the Microsoft Word documents
  5. Copy one of the following commands (based on your operating system) into your terminal and press Enter to run

Linux and macOS (Bash)

bash
find -name "*.docx" -type f -exec sh -c '
      for f; do
         pandoc --extract-media=./ -f docx -t markdown -o "${f%.*}.md" "$f"
      done
   ' find-sh {} +

Windows (PowerShell)

powershell
Get-ChildItem . -Filter *.docx |
Foreach-Object {
    pandoc --extract-media=./ --from docx --to markdown $_ -o $_.Name.Replace('.docx', '.md')
}

Relevant Configurations

Pandoc accepts a range of command line arguments to control the conversion process. Here, we'll mention a few that are relevant to the example above.

  • --extract-media=./ is used to extract the images from the Microsoft Word documents and store them in a subfolder named media
  • -t markdown converts the Microsoft Word documents to Pandocโ€™s Markdown. You can also use -t gfm to convert to GitHub Flavored Markdown

Note that you may want to review the converted Markdown files to ensure that the conversion was successful. Then, You may want to delete the original Microsoft Word documents.


User Recipes Migrating From Obsidian (`docs/user/recipes/migrating-from-obsidian.md`)

Coming from Obsidian

Good news: an Obsidian vault is, for the most part, already a Foam workspace. Both store your notes as plain Markdown files in a directory, so in most cases you can just open your vault folder in VS Code with the Foam extension installed and start working.

Try it in 30 seconds

  1. Install the Foam extension in VS Code.
  2. File โ†’ Open Folderโ€ฆ and pick your existing Obsidian vault.
  3. That's it โ€” wikilinks, backlinks, the graph, and tags should all work.

You can keep using Obsidian on the same folder side-by-side. Foam doesn't change your files unless you ask it to.

What works the same

Obsidian Foam
[[wikilinks]] [wikilinks]
![[embeds]] [embeds]
[[note#^block-id]] [block-anchors]
Backlinks panel [backlinking]
Graph view [graph-view]
Daily notes [daily-notes]
Templates [templates]
Tags (#tag) [tags]
Frontmatter / aliases [note-properties]

What's different or missing

These are the things most likely to surprise you:

  • Live preview / WYSIWYG editing. Foam uses VS Code's standard split editor + preview pane rather than inline rendering.
  • Community plugins. Obsidian plugins don't run in Foam. Foam relies on the wider VS Code extension ecosystem instead โ€” see [recommended-extensions].
  • Canvas. Not supported (tracked in #1450).
  • Tasks plugin syntax. Not supported (tracked in #1466).
  • Callouts / Admonitions (> [!note]). Not rendered natively; works as a normal blockquote.
  • Wikilink resolution. Foam and Obsidian agree on most cases but resolve identifier-style links slightly differently. See the comparison table in [wikilinks].

A few practical tips

  • .obsidian/ folder. Leave it alone if you're keeping both tools. Add it to .gitignore if you only want to track notes.
  • Templates. Foam templates live in .foam/templates/ โ€” see [templates] for the format.
  • Don't see something here? Search open issues โ€” chances are someone has already asked.

User Recipes Migrating From Onenote (`docs/user/recipes/migrating-from-onenote.md`)

Migrating from OneNote

This guide mostly duplicates the instructions at the repo for the PowerShell script.

Summary

The powershell script 'ConvertOneNote2MarkDown-v2.ps1' will utilize the OneNote Object Model on your workstation to convert all OneNote pages to Word documents and then utilizes PanDoc to convert the Word documents to Markdown (.md) format. It will also:

  • Create a folder structure for your Notebooks and Sections.
  • Process pages that are in sections at the Notebook, Section Group and 1st Nested Section Group levels.
  • Allow you you choose between putting all Images in a central '/media' folder for each notebook, or in a separate '/media' folder in each folder of the hierarchy.
  • Fix image references in the resulting .md files, generating relative references to the image files within the markdown document.
  • A title, description, and date header will be added to each file as well.
  • And more (see details at repo)!

Usage

  1. Start the OneNote application. All notebooks currently loaded in OneNote will be converted.
  2. It is advised that you install Onetastic and the attached macro, which will automatically expand any collapsed paragraphs in the notebook. They won't be exported otherwise.
    • To install the macro, click the New Macro Button within the Onetastic Toolbar and then select File -> Import and select the .xml macro included in the release.
    • Run the macro for each Notebook that is open
  3. For the next sections, it is highly recommended that you use VS Code, and its embedded PowerShell terminal, as this allows you to edit and run the script, as well as check the results of the .md output all in one window.
  4. Whatever you choose, you will need to do the following:
    1. Clone the script to your computer (see here, if you're unfamiliar with git).

    2. Once cloned, navigate to the repo folder. In VS Code, use File -> Add Folder to Workspace, right click on the folder in the left side bar and click Open In Integrated Terminal.

    3. Run the script by executing
      .\ConvertOnenote2Markdown-v2

      • if you receive an error, try running this line to bypass security:

      Set-ExecutionPolicy Bypass -Scope Process

      • if you still have trouble, try running both Onenote and Powershell as an administrator.
  5. It will ask you for the path to store the markdown folder structure. Please use an empty folder. If using VS Code, you might not be able to paste the filepath - right click on the blinking cursor and it will paste from clipboard. Attention: use a full absolute path for the destination.
  6. Read the prompts carefully to select your desired options. If you aren't actively editing your pages in Onenote, it is HIGHLY recommended that you don't delete the intermediate word docs, as they take 80+% of the time to generate. They are stored in their own folder, out of the way. You can then quickly re-run the script with different parameters until you find what you like.
  7. Sit back and wait until the process completes.
  8. To stop the process at any time, press Ctrl+C.
  9. If you like, you can inspect some of the .md files prior to completion. If you're not happy with the results, stop the process, delete the .md and re-run with different parameters.
  10. At this point, you should be ready to load the new directory into Foam!

User Recipes Multi Root Workspaces (`docs/user/recipes/multi-root-workspaces.md`)

Multi-root workspaces

A VS Code multi-root workspace has several folders side by side. A common setup is a knowledge base you add to each of your projects, so both your own notes and the project's notes are at hand:

text
my-project.code-workspace
โ”œโ”€โ”€ my-project/       โ† first folder
โ””โ”€โ”€ knowledge-base/   โ† your knowledge base, added to every workspace

Foam indexes every folder, so links and the graph work across them. What needs care is where new notes go: by default, Foam creates them in the first folder.

Settings

Put Foam settings in the .code-workspace file or in your user settings. In a multi-root workspace, VS Code ignores them in a folder's own .vscode/settings.json.

Templates

Foam looks for [templates] in the templates folder (.foam/templates by default) of every folder in the workspace:

  • Foam: Create New Note From Template lists the templates of all folders. When two folders have a template with the same name, it shows which folder each one is in.
  • For the special new-note and daily-note templates, Foam checks each folder in the order they're listed and uses the first one it finds.
  • New templates are created in the first folder.

Create notes in a specific folder

A template's filepath can name the folder a note goes in: start it with / and the folder's name. For example, if you want all new notes to go in knowledge-base/inbox/, your new-note template, knowledge-base/.foam/templates/new-note.md, would look like this:

markdown
---
foam_template:
  filepath: '/knowledge-base/inbox/$FOAM_SLUG.md'
---

# $FOAM_TITLE

Foam: Create New Note, and Ctrl+clicking a [[placeholder]], then create the note in knowledge-base/inbox/, in every workspace you add knowledge-base to, as long as the project has no new-note template of its own. To choose for each note instead, give the template another name, such as global-note.md, and pick it with Foam: Create New Note From Template.

[daily-notes] work the same way, with knowledge-base/.foam/templates/daily-note.md:

markdown
---
foam_template:
  filepath: '/knowledge-base/journal/$FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE.md'
---

# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE

A few things to know:

  • Use the folder's name on disk, with the same capitalization, not a different name you gave it in the .code-workspace file.
  • If no folder has that name, the path starts from the first folder: /journal/today.md goes in my-project/journal/.
  • The same template works when you open the knowledge base on its own: /knowledge-base/inbox/ is then its own inbox/ folder.

With the default settings, notes created without a filepath, such as with Foam: Create New Note when there's no new-note template, go in the first folder.


User Recipes Notes In A Subfolder (`docs/user/recipes/notes-in-a-subfolder.md`)

Keep your notes in a subfolder

Sometimes your notes live inside a bigger project rather than in a repository of their own โ€” a knowledge-base folder next to your source code, say:

text
my-project/
โ”œโ”€โ”€ knowledge-base/     โ† your notes
โ”œโ”€โ”€ src/
โ””โ”€โ”€ README.md

Foam works in this layout with three bits of configuration. There's no single "Foam root" setting: instead you tell Foam which files to index, where your templates live, and where new notes should go.

1. Index only your notes

In .vscode/settings.json:

json
{
  "foam.files.include": ["knowledge-base/**"]
}

Foam now builds the graph from your notes alone and ignores the rest of the project. Reload the window to apply it.

2. Point at your templates

json
{
  "foam.files.include": ["knowledge-base/**"],
  "foam.templates.folder": "knowledge-base/.foam/templates"
}

Your [templates] live at knowledge-base/.foam/templates/ rather than the project root.

3. Create new notes in the right place

By default, clicking a [[new-note]] placeholder creates the file at the top of the project. To send it to your notes folder instead, create knowledge-base/.foam/templates/new-note.md:

markdown
---
foam_template:
  filepath: 'knowledge-base/${FOAM_TITLE}.md'
---

# ${FOAM_TITLE}

Foam uses new-note.md whenever it creates a note without being told where to put it, so this covers Ctrl+clicking a placeholder as well as the Foam: Create New Note command.

For [daily-notes], do the same in knowledge-base/.foam/templates/daily-note.md:

markdown
---
foam_template:
  filepath: 'knowledge-base/journal/${FOAM_DATE_YEAR}-${FOAM_DATE_MONTH}-${FOAM_DATE_DATE}.md'
---

# ${FOAM_DATE_YEAR}-${FOAM_DATE_MONTH}-${FOAM_DATE_DATE}

Linking

[[wikilinks]] work exactly as they do anywhere else โ€” [[my-note]] finds knowledge-base/my-note.md, because Foam only indexes your notes folder.

The one thing to know is that a leading slash means "the folder you opened in VS Code", not "my notes folder". So [[/my-note]] and [my note](/my-note.md) point at my-project/my-note.md, which isn't where your notes are. Write [[/knowledge-base/my-note]] if you want the full path โ€” or just use [[my-note]], which is shorter and unambiguous.

This also means you can link out to the rest of the project: [the build script](/scripts/build.sh) does what you'd expect, and works on GitHub too.

What stays at the project root

When you delete a note, Foam moves it to .foam/trash/ at the top of the project, and [smart-folders] are saved under .foam/queries/. If you'd rather keep everything together, move those folders into your notes directory by hand โ€” Foam will recreate them at the project root next time, so it's a matter of taste rather than correctness.

  • [first-workspace] โ€” setting up a Foam workspace
  • [templates] โ€” the full template reference
  • [wikilinks] โ€” link syntax, including paths and identifiers

User Recipes Predefined User Snippets (`docs/user/recipes/predefined-user-snippets.md`)

Pre-defined User Snippets

This #recipe allows us to introduce slash-style commands to Foam, by using VS Code Snippets. Consider the below snippets:

json
{
  "Zettelkasten Id": {
    "scope": "markdown",
    "prefix": "/id",
    "description": "Zettelkasten Id",
    "body": [
      "${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND}"
    ]
  },
  "Current date": {
    "scope": "markdown",
    "prefix": "/date",
    "description": "Current date",
    "body": [
      "${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} ${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND}"
    ]
  }
}

Which would look like:

Using snippets enables Foam users to add [custom-snippets] themselves so they live alongside the first-class /commands.

Notes & Considerations

  • VS Code supplies "commands" already via the command palette
    • Consider the UX around this. Users less familiar with VS Code are more likely to be familiar with / to trigger a command menu. Experienced VS Code users may be more likely to favour the command palette.
  • We can use TabCompletionProvider and snippets and mix them (maybe) via the following VS Code setting: "editor.snippetSuggestions": "inline" | "top" | "bottom" | "none"
  • For more discussion, consult the PR here.

Simplifying Markdown Syntax

Some markdown syntax is difficult for users who have never authored markdown before. Take for example a checkbox/todo. The following syntax is required:

text
- [ ] Something todo...

We could provide snippets that expand out into the associated markdown syntax, like in the below GIF:

The JSON for these snippets can be found here.


User Recipes Real Time Collaboration (`docs/user/recipes/real-time-collaboration.md`)

Real-time Collaboration

This #recipe is here to just tell you that VS Code Live Share will allow you to collaborate live on your notes.


User Recipes Recipes (`docs/user/recipes/recipes.md`)

Recipes

A #recipe is a guide, tip or strategy for getting the most out of your Foam workspace!

Contribute

Take smart notes

Discover

Organise

Write

Version control

Publish

Collaborate

Workflow

Creative ideas

Creative ideas welcome!

See [how-to-write-recipes] and the contribution guide.

Other

Thought of a recipe but don't see a category for them? Add them here and we'll organise them once we detect a theme.

See [how-to-write-recipes] and the contribution guide.


User Recipes Search And Navigate Notes (`docs/user/recipes/search-and-navigate-notes.md`)

Search and Navigate Notes

This #recipe covers the ways you can find and navigate notes in Foam.

Quick Open

Press Cmd+P (Ctrl+P on Windows) and type a note name to open it directly. Partial matches work โ€” proj meet will find projects/meeting-notes.md.

Press Cmd+Shift+F (Ctrl+Shift+F on Windows) to search across all note content. Use the filter icon to restrict search to specific folders or file types.

Note Navigator Panel

The Note Navigator panel in the sidebar lets you browse and filter all notes in your workspace. Use the filter input to narrow by name, and switch between flat and grouped-by-folder views from the panel toolbar.

Other Panels

Foam adds several panels to the sidebar that help you discover notes:

  • Backlinks โ€” shows which notes link to the currently open note
  • Placeholders โ€” lists wikilinks that don't yet have a corresponding file
  • Orphans โ€” lists notes with no incoming or outgoing links (see [orphans])
  • Tags โ€” browse all tags and jump to tagged notes (see [tags])

Graph View

The [graph-view] gives a visual overview of your entire knowledge base. Click any node to open that note. You can also [[resource-filters|filter the graph]] to focus on a subset of notes by tag, type, path, or title.

Random Note

Run "Foam: Open Random Note" from the command palette to resurface forgotten notes.


User Recipes Shows Image Preview On Hover (`docs/user/recipes/shows-image-preview-on-hover.md`)

Shows Image Preview on Hover

This #recipe allows you to see a preview of an image on hover.

Use extension: Image preview to shows image preview in the gutter and on hover

It looks like this


User Recipes Take Notes From Mobile Phone (`docs/user/recipes/take-notes-from-mobile-phone.md`)

Take notes on mobile phones

This #recipe offers solutions to taking Foam notes on the go.

For the time being we have decided to not build a mobile app, but rely on third parties.

The most promising options are:

[GitJournal](https://gitjournal.io/)

Pros

  • Open source
  • Already a usable solution.
  • Provides functionality to edit, create, and browser markdown files.
  • Support journal mode, todo lists, and free writing
  • Syncs to GitHub repo
  • Supports Wikilinks
  • Supports Backlinks
  • Developer is happy to prioritize Foam compatibility

Cons

  • Doesn't generate link reference lists (but this is ok, since [workspace-lint] as a GitHub action can solve this)
  • Not as sleek as Apple/Google notes, some keyboard state glitching on Android, etc.
  • Lack of control over roadmap. Established product with a paid plan, so may not be open to Foam-supportive changes and additions that don't benefit most users.

Verdict: Good. By far best effort/outcome ratio would be to help improve GitJournal rather than building a bespoke mobile app.

GitHub Codespaces

Pros

  • Works out of the box just like the desktop app

Cons

  • not generally available quite yet
  • Pricing

For a quick demo, see https://www.youtube.com/watch?v=KI5m4Uy8_4I.

Verdict: Good. Pricing should be reasonable for taking notes on the fly. Harder to assess for people who would constantly use Foam from mobile phone.

Bespoke mobile app for Foam

Given we already have a solution, why would we spend time and effort building a bespoke mobile app?

  • Taking notes on the go is a key part of a good note taking process, and the process should feel effortless
  • Having a custom app could help us support key user workflows in a more Foam-specific manner

If such an app was worth building, it would have to have the following features:

  • Instant loading and syncing for quick notes
  • Sleek, simple, beautifully designed user experience.
  • Ability to search and navigate forward links and back links (only in paid GitJournal version)
  • Killer feature that makes it the best note taking tool for Foam (?)

Given the effort vs reward ratio, it's a low priority for core team, but if someone wants to work on this, we can provide support! Talk to us on the #mobile-apps channel on Foam Discord.


User Recipes Web Clipper (`docs/user/recipes/web-clipper.md`)

Web Clipper

This #recipe allows you to convert any web content into Markdown for storing them in your notes.

There are a couple of options when it comes to clipping web pages:

  • Web Clipper

    • This is a Web Clipper as a VSCode extension, takes a webpage URL and outputs Markdown. Uses mercury
  • Markdown Clipper

    • A Firefox and Google Chrome extension to clip websites and download them into a readable markdown file.
  • Web Clipper

    • A Firefox, Chrome and Edge extension to clip websites and save them directly to the GitHub repository into a readable markdown file.

User Recipes Write Your Notes In Github Gist (`docs/user/recipes/write-your-notes-in-github-gist.md`)

Write your notes in GitHub Gist

This #recipe will allow you to persist your notes in a GitHub repository, and automatically sync changes without needing to manually commit/push/pull, then GistPad might be an option worth exploring.

GistPad is a Visual Studio Code extension that allows you to edit your GitHub gists and repos, without needing to clone anything locally.

It provides support for editing Foam workspaces, complete with [[link]] completion/navigation, daily pages, pasting images and backlinks.

Getting started

To start using GistPad for your Foam-based knowledge base, simply perform the following steps:

  1. Download the GistPad extension and then re-start Visual Studio Code

  2. Run the GistPad: Sign In command and then complete the authentication flow using your GitHub account

  3. Run the GistPad: Open Repository command and select the Create repo from template... or Create private repo from template... depending on your preference

  4. Select the Foam-style wiki template, and then specify a name for your Foam workspace (e.g. my-foam-notes, johns-knowledge-base)

Your new repo will be created in your GitHub account, and the Foam welcome page will be automatically opened. If you already have an existing Foam workspace in GitHub, then when you run step #3 above, simply select or specify the name of the GitHub repository instead.

Note: If you have any and all feedback on how GistPad can be improved to support your Foam workflow, please don't hesitate to let us know! ๐Ÿ‘

Managing your workspace

Once you've opened/created the Foam repository, it will appear in the Repositories view of the GistPad tab (the one with the little notebook icon). From this tree view, you can add/edit/delete/rename new pages, upload local files, as well as view the backlinks for each page (they appear as child notes of a page).

Editing your workspace

When you create or open a page, you can edit the markdown content as usual, as well as paste images, and create [[links]] to other pages. When you type [[, you'll receive auto-completion for the existing pages in your workspace, and you can also automatically create new pages by simply creating a link to it.

Since you're using the Visual Studio Code markdown editor, you can benefit from all of the rich language services (e.g. syntax highlighting, header collapsing), as well as the extension ecosystem (e.g. Emojisense).

When editing a file, you can easily navigate [[links]] by hovering over them to see a preview of their contents and/or cmd+clicking on them in order to jump to the respective page. Furthermore, when you add a link to a page, a backlink is automatically added to it.

You can view a page's backlinks using either of the following techniques:

  1. Expanding the file's node in the Repositories tree, since it's child nodes will represent backlinks. This makes it easy to browse your pages and their backlinks in a single hierarchical view.

  2. Opening a file, and then viewing it's backlinks list at the bottom of the editor view. This makes it easy to read a page and then see its backlinks in a contextually rich way.

Daily Pages

In addition to creating arbitrary pages, you can use GistPad for journaling or capturing daily notes. Simply click the calendar icon in the Repositories tree, which will open up the page that represents today. If the page doesn't already exist, then it will be created in the workspace before being opened.


User Tools Cli (`docs/user/tools/cli.md`)

Foam CLI

The foam command line tool lets you work with your Foam workspace from the terminal โ€” search, inspect, and manage notes without opening VS Code.

Installation

bash
npm install foam-cli

Usage

text
foam <command> [options]

Set the FOAM_WORKSPACE environment variable to avoid typing --workspace on every command:

bash
export FOAM_WORKSPACE=/path/to/your/notes

All commands accept --format json for machine-readable output, useful for scripting.

Files outside the workspace

Commands only change files inside the workspace. If a path leads outside it, for example through a folder that is a symlink to somewhere else, the command stops with Cannot write outside the workspace: instead of writing there. To work with those files, run the command with --workspace pointing at the folder they live in.

Commands

Command Description
[daily] Show or create the daily note for a date
[grep] Search note content by pattern
[links] Show links to and from a note
[lint] Check notes for issues
[list] List notes, tags, orphans, placeholders, and more
[mcp] Expose the workspace to AI agents via MCP
[note] Show, create, move, or delete notes
[outline] Show the heading structure of a note
[rename] Rename notes, tags, sections, or block anchors
[search] Search notes by title, tag, or frontmatter
[tag] List, rename, or search tags
[update] Check for a newer version of foam-cli

User Tools Foam Logging In Vscode (`docs/user/tools/foam-logging-in-vscode.md`)

Foam logging in VsCode

The Foam extension logs details about what its doing in vscode's Output tab.
Generally this is only useful if you're reporting an issue about Foam.

  1. To show the tab, click on View > Output.
  2. In the dropdown on the right of the tab, select Foam.

When reporting an issue about Foam, set the log level to Debug:

Change the log level for the session

Execute the command Foam: Set log level.

Change the default logging level

  1. Open workspace settings (cmd+,, or execute the Preferences: Open Workspace Settings command)
  2. Look for the entry Foam > Logging: Level

User Tools Orphans (`docs/user/tools/orphans.md`)

Orphaned Notes

Foam helps you to find orphans: notes that have neither forward links nor backlinks.

Orphans can be found in the Orphans panel.

Two settings allows you to control the behaviour of the Orphans panel:

  • foam.orphans.exclude: list of glob patterns that will be used to exclude directories. For example, a value of ["journal/**/*"] would exclude your daily notes.
  • foam.orphans.groupBy: sets the default view mode of the Orphans panel: either groups by folder (by default), or lists all orphans. The view can be toggled on the fly from the panel, but it won't overwrite the setting.

To list orphans from the terminal, see [list|foam list orphans].


User Tools Telemetry (`docs/user/tools/telemetry.md`)

Telemetry

Foam collects anonymous usage data to help understand how Foam is used and to prioritize improvements. This page describes what is sent, what is never sent, and how to opt out.

What is never collected

This applies to every Foam component:

  • Note content, titles, file names, or tag names
  • Wikilink targets, search queries, or any text you typed
  • Folder paths or any user-defined string
  • Free-text configuration values
  • Command arguments, CLI flag values, or MCP tool arguments
  • Stack traces or error messages (only the error class name is sent)
  • Anything that could identify you personally

What is collected

Every event includes:

  • Which component sent it (vscode, cli, mcp)
  • The version of that component and of @foam/core
  • Your OS family โ€” CLI/MCP send os.platform (darwin, linux, win32) and Node version; VS Code's library reports OS and version via its own common.* properties (see Common properties)
  • An anonymous identifier โ€” CLI and MCP share an installation UUID generated locally on first run. VS Code uses the pseudonymous machine ID provided by Microsoft's @vscode/extension-telemetry library (common.vscodemachineid); we cannot disable that and we never link it to the CLI/MCP ID.

At a high level:

  • VS Code extension sends a small set of session events: that Foam loaded, which commands you ran, your enum/boolean settings, and bucketed workspace size (e.g. "201-500 notes"). No file names, paths, or note titles.
  • CLI sends one event per invocation: which command ran, how long it took (bucketed: 0-10ms, 11-50ms, 51-300ms, 301-1000ms, 1-5s, 5-30s, 30s+), and bucketed workspace size.
  • MCP sends one event per tool call the LLM makes: which tool was called, how long it took, and whether it succeeded. Tool arguments and returned content are never sent.

The full event list is at the bottom of this page.

How to opt out

VS Code extension

Foam respects VS Code's global telemetry setting. Open Settings, search for telemetry.telemetryLevel, and set it to off.

CLI

On the first run, the CLI prints a notice describing what is collected and asks you to confirm. The default is enabled โ€” pressing Enter accepts it. Your choice is saved in ~/.config/foam/config.json and not asked again.

If the CLI is run non-interactively (CI, piped input, MCP launched by Claude Desktop / Cursor, etc.), it cannot prompt โ€” it prints a disclosure to stderr and defaults to enabled for that session only. No choice is persisted, so the next interactive CLI run will still ask. To make the choice durable in a non-interactive context, set FOAM_TELEMETRY=0 or FOAM_TELEMETRY=1 in the environment, or edit ~/.config/foam/config.json directly.

You can change your choice at any time:

  • Set FOAM_TELEMETRY=0 in your environment, or
  • Edit ~/.config/foam/config.json and set "telemetry.enabled": false

MCP

MCP runs as a child of the CLI (foam mcp ..., typically configured in Claude Desktop / Cursor / mcp.json). It inherits the CLI's opt-out โ€” set FOAM_TELEMETRY=0 in the MCP server's environment, or edit ~/.config/foam/config.json.

Where the data goes

All events are sent to a single Azure Application Insights resource owned by the Foam project. Connection strings used to send data are not secrets โ€” they only permit writing events, never reading them. Aggregated, anonymized usage data may be shared with the Foam community to inform roadmap discussions. Raw event data is not shared.

Full event list

Common properties

The properties below are attached to every event.

Property Components Example Description
foam.component CLI, MCP, VS Code cli, mcp, vscode Which Foam component emitted the event
foam.version CLI, MCP, VS Code 0.26.0 Version of that component
foam.coreVersion CLI, MCP, VS Code 0.4.1 Version of @foam/core
node.version CLI, MCP 22.5.0 Node runtime version (CLI/MCP only โ€” VS Code reports it via common.platformversion)
os.platform CLI, MCP darwin, linux, win32 Operating system family (CLI/MCP only โ€” VS Code reports it via common.os)
installationId CLI, MCP (random UUID) Anonymous installation ID shared across CLI and MCP. Sent on the App Insights envelope as the ai.user.id tag, not as an event property. Omitted on the cli.first-run event.

VS Code events additionally carry the common.* properties added by Microsoft's @vscode/extension-telemetry library โ€” these are not controlled by Foam:

Property Description
common.extname Extension name (foam.foam-vscode)
common.extversion Extension version
common.vscodemachineid Pseudonymous machine identifier generated by VS Code. Distinct from Foam's installationId; we cannot disable it.
common.vscodesessionid Per-session identifier generated by VS Code
common.vscodeversion VS Code version
common.vscodecommithash VS Code commit hash
common.os OS running VS Code
common.platformversion OS/platform version
common.product VS Code host (desktop, github.dev, codespaces, โ€ฆ)
common.uikind web or desktop
common.remotename Type of remote connection if any (ssh, wsl, dev-container, or other)
common.nodeArch Node architecture (e.g. arm64, x64, or web)

You can disable VS Code's telemetry โ€” and therefore everything Foam sends from VS Code โ€” via the global telemetry.telemetryLevel setting (off). The CLI and MCP opt-out is separate and is described above.

Bucket scales

  • Duration: <10ms, <50ms, <500ms, <5s, <30s, 30s+
  • Workspace size (note count): 0, 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-2000, 2001-5000, 5001-10000, 10000+

VS Code events (`vscode.*`)

Event When it fires Properties
vscode.session-started Once per extension activation none
vscode.session-with-command First Foam command in a session none
vscode.session-with-note First markdown file opened in a session none
vscode.command Every Foam command invocation command (identifier only โ€” no arguments)
vscode.config-snapshot Once per session selected enum/boolean settings โ€” e.g. graph.onStartup, completion.linkFormat, links.hover.enable, graph.viewsConfigured (count, not names). Free-text settings are never included.
vscode.workspace-stats Once per session noteCount and attachmentCount (both bucketed), numTemplates (count of files in the templates folder), hasDailyNoteTemplate
vscode.error An unhandled error in a Foam feature context (feature name), errorType (error class name only โ€” no message, no stack)

CLI events (`cli.*`)

Event When it fires Properties
cli.command-invoked Once per CLI invocation that runs a known command, after it completes. Skipped for --help, --version, foam config, and unknown commands (typos) โ€” those never emit telemetry. command, durationBucket, exitCode. When the command threw an error caught by the dispatcher, also errorType (error class name only โ€” no message, no stack) and errorContext (where it was caught โ€” dispatch). Some commands attach extra properties โ€” e.g. note create and daily --create attach template-type (one of default, daily-note, custom) and template-format (md / js). Both are omitted when no template was applied (e.g. a note create that fell back to the minimal # title body). daily additionally attaches mode: create when a new daily note was written this invocation, open for read-only lookups and for --create against a note that already existed (nothing was written).
cli.first-run At most twice per installation: once for the strongest consent outcome we've recorded so far. Anonymous โ€” carries no installationId, no os.platform, no node.version. An install that runs headlessly first (e.g. MCP) fires once with default_on; if the same install later answers an interactive CLI prompt, a second event fires with granted / declined (the "upgrade"). Subsequent runs of either kind are deduped via state.json. consent: granted / declined (from an interactive prompt), or default_on (non-interactive first encounter)

MCP events (`mcp.*`)

Event When it fires Properties
mcp.session-started Once per server start client (e.g. claude-desktop, cursor, omitted if not exposed); noteCount and attachmentCount (both bucketed); mode (read or read-write โ€” read-write is set when the CLI is started with --allow-writes; the default is read)
mcp.session-with-tool First tool call in a session none
mcp.tool-invoked Every tool call (sent unsampled) tool (name only โ€” arguments never recorded), durationBucket, outcome (success / error)
mcp.error An unhandled error during MCP dispatch. Rare in practice: tool handlers are wrapped to convert throws into structured isError: true results, which surface as outcome: 'error' on mcp.tool-invoked. mcp.error is the safety net for anything that escapes that wrapping. context, tool, errorType

User Tools Workspace Lint (`docs/user/tools/workspace-lint.md`)

Lint

To store your personal knowledge graph in markdown files instead of a database, we need some additional tooling to create and maintain relationships with notes.

Foam Lint (inspired by Andy Matuschak's note-link-janitor) helps you migrate existing notes to Foam, and maintain your Foam's health over time.

Currently, Foam Lint helps you to:

  • Ensure your [link-reference-definitions] are up to date
  • Ensure every document has a well-formatted title (required for Markdown Links, Markdown Notes, and Foam Gatsby Template compatibility)

The link reference definition check only runs when foam.edit.linkReferenceDefinitions is set to withExtensions or withoutExtensions in your workspace settings. It is off by default, so if you want Lint to keep definitions up to date โ€” from VS Code, a git hook, or a GitHub action โ€” turn it on first. See [link-reference-definitions].

In the future, Foam Lint can help you with

  • Lint, format and structure notes
  • Rename and move notes around while keeping their references up to date.

Using Lint from VS Code (Experimental)

Execute the "Foam: Lint Workspace" command from the command palette.

Using lint from command line

Foam Lint can be installed from NPM and executed as a standalone CLI tool:

sh
> npm install foam-cli
> foam lint --workspace path/to/workspace

You can run Foam Lint as a git hook on every commit to ensure your workspace links are up to date. This can be especially helpful if you edit your markdown documents from other apps.

You can also run Foam Lint from a GitHub action to ensure that all changes coming to your workspace are up to date. This can be useful when editing your Foam notes from mobile (i.e. via GitJournal), or your Foam has multiple contributors and you want to ensure that all changes are correctly integrated.


User Tools Cli Daily (`docs/user/tools/cli/daily.md`)

foam daily

Show or create the daily note for a date.

text
foam daily [options]

Without --create, the command only reports whether the note exists. With --create, it writes the note if it doesn't exist yet, using your daily note template if one is configured.

Options

Option Description
--date <YYYY-MM-DD> Date to target (default: today)
--create Create the note if it doesn't exist
--path-only Print only the resolved file path (useful for scripting)
--trust Run a JavaScript daily note template (daily-note.js)
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

Examples

Show today's daily note status:

bash
foam daily
# journal/2024-01-15.md  [exists]

Create today's daily note:

bash
foam daily --create

Get the path to a specific date's note (for scripting):

bash
foam daily --date 2024-01-10 --path-only
# /home/user/notes/journal/2024-01-10.md

Open today's note in your editor:

bash
$EDITOR $(foam daily --create --path-only)

Daily note path

If your daily note template (.foam/templates/daily-note.md or .foam/templates/daily-note.js) sets a file path, the note goes there. Otherwise the deprecated foam.openDailyNote.* settings in .vscode/settings.json decide, and without them notes go to journal/YYYY-MM-DD.md โ€” the same place VS Code puts them. The template, when there is one, always provides the content.

The path must stay inside the workspace, even through symlinks: otherwise foam daily --create stops with an error telling you to check the template's file path and the foam.openDailyNote.directory setting.

See [templates] for how to configure daily note templates.

JavaScript templates

A JavaScript template runs code on your computer, so when your workspace has .foam/templates/daily-note.js, foam daily stops with an error unless you add --trust:

bash
foam daily --create --trust

Only add --trust if you wrote the template yourself, or trust whoever did.


User Tools Cli Grep (`docs/user/tools/cli/grep.md`)

foam grep

Search note content by pattern.

text
foam grep <pattern> [options]

Searches the text content of all notes in the workspace. The pattern is matched case-insensitively as a regular expression. Unlike [[search|foam search]], this command searches the full text of each note rather than indexed metadata.

Options

Option Description
--context <n> Show n lines of surrounding context around each match
--no-line-number Omit line numbers from output
--limit <n> Max number of matching files to return (default: 20)
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

Examples

Search for a word across all notes:

bash
foam grep "quantum"
# physics/quantum-mechanics.md:3: Quantum mechanics describes...
# research/reading-list.md:12: See also quantum entanglement

Search with context lines:

bash
foam grep "TODO" --context 2

Limit results:

bash
foam grep "meeting" --limit 5

Use in a script (JSON output):

bash
foam grep "action item" --format json

Show links to and from a note.

text
foam links (<identifier> | --path <path>) [options]

Displays the outgoing links (notes this note links to) and incoming links (notes that link to this note, also called backlinks). The command can also be invoked as foam connections.

Options

Option Description
--path Target note by exact file path instead of identifier
--outgoing Show only outgoing links
--incoming Show only incoming links (backlinks)
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

By default, both outgoing and incoming links are shown.

Examples

Show all links for a note:

bash
foam links my-note
# Outgoing (2):
#   โ†’ related-topic    notes/related-topic.md
#   โ†’ another-note     notes/another-note.md
#
# Incoming (1):
#   โ† index            index.md

Show only backlinks:

bash
foam links my-note --incoming

Target by file path:

bash
foam links --path notes/my-note.md

Get links as JSON for scripting:

bash
foam links my-note --format json

Note identifiers

Foam identifies notes by the shortest unique part of their filename (without extension). For example, a note at projects/alpha/notes.md might be identified as notes if that's unique, or alpha/notes if there's ambiguity. Use [[note|foam note id]] to check how a note is identified.

See also [backlinking] for the backlinks panel in VS Code.


User Tools Cli Lint (`docs/user/tools/cli/lint.md`)

foam lint

Check notes for issues.

text
foam lint [options]

Scans all notes in the workspace and reports problems such as missing headings or stale link reference definitions. With --fix, auto-fixable issues are corrected in place.

Which rules run depends on your workspace settings โ€” see Rules.

Exits with code 2 when issues are found (and no --fix), making it easy to use in CI pipelines.

Options

Option Description
--fix Apply all auto-fixable issues
--rule Run only the given rule (can be repeated)
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

Rules

Rule ID Description Auto-fixable
missing-heading Notes without a top-level heading Yes
stale-definitions Link reference definitions that are outdated or unused Yes

stale-definitions only runs when [link-reference-definitions] are enabled for the workspace, the same as the "Foam: Lint Workspace" command in VS Code. The foam.edit.linkReferenceDefinitions setting is off by default, so turn it on in your workspace's .vscode/settings.json to have the rule report and fix definitions:

json
{
  "foam.edit.linkReferenceDefinitions": "withExtensions"
}

Asking for the rule with --rule stale-definitions while the setting is off is a command error (exit 1).

Exit codes

Code Meaning
0 No issues found
1 Command error
2 Issues found

Examples

Check the workspace for issues:

bash
foam lint
# notes/my-note.md
#   5:1  warning  Missing heading  missing-heading
#
# 1 problem (0 errors, 1 warning, 1 fixable with --fix)

Fix all auto-fixable issues:

bash
foam lint --fix

Run only one rule:

bash
foam lint --rule missing-heading

Use in CI:

bash
foam lint || echo "Lint issues found โ€” please fix before merging"

Get results as JSON:

bash
foam lint --format json

See also [workspace-lint] for running lint from VS Code.


User Tools Cli List (`docs/user/tools/cli/list.md`)

foam list

List notes, tags, orphans, placeholders, dead-ends, or templates.

text
foam list <what> [options]

What to list

Value Description
notes All notes in the workspace
tags All tags used across notes
orphans Notes with no links in or out
deadends Notes with no outgoing links
placeholders Wikilinks that don't resolve to an existing note
templates Note templates in .foam/templates/

Options

Option Description
--type <type> (notes) Filter by resource type: note (any markdown file), attachment, image
--tag <tag> (notes) Filter by tag โ€” repeat for multiple tags (AND logic)
--limit <n> (notes, tags) Maximum number of results
--prefix <str> (tags) Filter by tag prefix
--sort <count|name> (tags) Sort order (default: name)
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

Examples

List all notes:

bash
foam list notes

List notes with a specific tag:

bash
foam list notes --tag project

List notes tagged both project and active:

bash
foam list notes --tag project --tag active

List all tags sorted by usage count:

bash
foam list tags --sort count

List tags starting with project/:

bash
foam list tags --prefix project/

Find orphaned notes (no links in or out):

bash
foam list orphans

Find broken wikilinks (links that don't resolve to a note):

bash
foam list placeholders

List available templates:

bash
foam list templates

See also [orphans] for the orphaned notes panel in VS Code, and [tags] for tag management in the editor.


User Tools Cli Mcp (`docs/user/tools/cli/mcp.md`)

foam mcp

Run a Model Context Protocol server over stdio so AI agents (Claude Desktop, Cursor, Zed, โ€ฆ) can read and traverse your Foam workspace as a knowledge graph instead of grepping files.

text
foam mcp [options]

The server stays running for the whole agent session: it loads the workspace once at startup and watches the filesystem so subsequent tool calls reflect notes you edited in your editor.

Options

Option Description
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--allow-writes Let the agent create, edit, delete, and rename notes and tags. Off by default.

By default the server is read-only. Add --allow-writes to let the agent modify your notes.

Connecting an AI client

Most MCP clients accept a JSON configuration similar to this โ€” point them at foam-cli:

json
{
  "mcpServers": {
    "foam": {
      "command": "npx",
      "args": ["foam-cli", "mcp", "--workspace", "/path/to/your/notes"]
    }
  }
}

For Claude Desktop the file is ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows. Restart the client after editing.

To allow the agent to edit notes too, add "--allow-writes" to the args array.

What the agent can do

Read tools (always available):

  • Browse the workspace (list_resources, get_resource, read_resource, get_workspace_info)
  • Follow the link graph (get_connections, traverse_graph, get_orphans, get_deadends, get_placeholders, get_graph_summary)
  • Search by tag, frontmatter property, or text (list_tags, search_by_tag, search_by_property, search_resources)
  • Inspect note structure (get_outline)

Write tools (only with --allow-writes):

  • Create, update, delete, and move notes (create_resource, update_resource, delete_resource, move_resource)
  • Add, remove, or rename tags (add_tags, remove_tags, rename_tag)

Write tools only change files inside the workspace. A change that would land outside it, including through a symlinked folder, fails with Cannot write outside the workspace.

Logs

Foam writes its logs to stderr (stdout is reserved for MCP traffic). To increase verbosity for troubleshooting:

bash
FOAM_LOG_LEVEL=debug foam mcp --workspace /path/to/notes

Valid values: debug, info, warn, error (default).


User Tools Cli Note (`docs/user/tools/cli/note.md`)

foam note

Show, create, move, or delete notes.

text
foam note <subcommand> [options]

Subcommands

show

Print metadata for a note.

bash
foam note show (<identifier> | --path <path>) [--links] [--content]
Option Description
--links Include outgoing and incoming links in the output
--content Print the full note content instead of metadata
--path Target by exact file path

Example:

bash
foam note show my-note
# ID:       my-note
# Title:    My Note
# Path:     notes/my-note.md
# Type:     note
# Tags:     #project #active

id

Print the Foam identifier for a note.

bash
foam note id (<identifier> | --path <path>)

Useful for resolving how Foam identifies a note when there could be ambiguity:

bash
foam note id my-note
# my-note

create

Create a new note.

bash
foam note create [options]
Option Description
--title <title> Note title (default: untitled)
--dir <dir> Directory relative to workspace root
--property <key=val> Add a frontmatter property (can be repeated)

If a new-note template exists in .foam/templates/, it is used to generate the note content and file path. See [templates] for details.

Example:

bash
foam note create --title "Meeting Notes" --dir meetings
# Created: meetings/meeting-notes.md  (id: meeting-notes)

move

Move a note to a new path, rewriting all wikilinks that point to it.

bash
foam note move (<identifier> | --path <path>) --to <new-path>
Option Description
--to Destination path (relative to workspace root)
--path Target by exact file path

Example:

bash
foam note move my-note --to archive/my-note.md
# Moved: notes/my-note.md โ†’ archive/my-note.md  (id: my-note, 3 links updated)

delete

Delete a note.

bash
foam note delete (<identifier> | --path <path>) [--force] [--permanent]

By default, deleted notes are moved to .foam/trash/ rather than permanently removed. You will be prompted to confirm unless --force is passed.

Option Description
--force Skip confirmation prompt
--permanent Delete permanently instead of moving to trash
--path Target by exact file path

Example:

bash
foam note delete old-draft
# Delete notes/old-draft.md? [y/N] y
# Trashed: notes/old-draft.md โ†’ .foam/trash/notes/old-draft.md

Global options

Option Description
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

User Tools Cli Outline (`docs/user/tools/cli/outline.md`)

foam outline

Show the heading structure of a note.

text
foam outline (<identifier> | --path <path>) [options]

Prints the headings in a note in order, indented to reflect their level. Useful for getting a quick overview of a long note's structure.

Options

Option Description
--path Target note by exact file path instead of identifier
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

Examples

Show the outline of a note:

bash
foam outline my-note
# Introduction
  ## Background
  ## Goals
# Implementation
  ## Phase 1
  ## Phase 2
# Conclusion

Get the outline as JSON (includes line numbers):

bash
foam outline my-note --format json

Target by file path:

bash
foam outline --path notes/my-note.md

User Tools Cli Rename (`docs/user/tools/cli/rename.md`)

foam rename

Rename notes, tags, sections, or block anchors โ€” with automatic link rewriting.

text
foam rename <subcommand> [options]

All subcommands update every wikilink that references the renamed item across the whole workspace.

Subcommands

note

Rename a note file and rewrite all wikilinks pointing to it.

bash
foam rename note (<identifier> | --path <path>) <new-name>
Option Description
--target-path Move the file to a different directory
--path Target by exact file path

Example:

bash
foam rename note old-name new-name
# Renamed: notes/old-name.md โ†’ notes/new-name.md  (id: new-name, 4 links updated)

Move to a different directory at the same time:

bash
foam rename note my-note my-note --target-path archive/

tag

Rename a tag and all its hierarchical children across the workspace.

bash
foam rename tag <old-tag> <new-tag>
Option Description
--force Skip confirmation if the rename would merge two existing tags

Example:

bash
foam rename tag project/active project/in-progress
# Renamed: #project/active โ†’ #project/in-progress  (3 notes updated)

section

Rename a heading section in a note and rewrite all [[note#Section]] links pointing to it.

bash
foam rename section (<identifier> | --path <path>) <old-label> <new-label>
Option Description
--path Target by exact file path

Example:

bash
foam rename section my-note "Background" "Context"
# Renamed section "Background" โ†’ "Context" in notes/my-note.md  (2 links updated)

block

Rename a block anchor in a note and rewrite all [[note#^id]] links pointing to it.

bash
foam rename block (<identifier> | --path <path>) <old-id> <new-id>
Option Description
--path Target by exact file path

Example:

bash
foam rename block my-note key-insight better-insight
# Renamed block ^key-insight โ†’ ^better-insight in notes/my-note.md  (1 link updated)

Global options

Option Description
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

See also [wikilinks] for how wikilinks work, and [block-anchors] for block anchor syntax.


User Tools Cli Search (`docs/user/tools/cli/search.md`)

Search notes by title, alias, tag, or frontmatter property.

text
foam search [<query>] [options]

Searches the workspace index. The optional <query> is matched against note titles and aliases (substring, case-insensitive). Combine it with --tag and --property filters to narrow results.

For full-text content search, use [[cli-grep|foam grep]] instead.

Options

Option Description
<query> Substring to match against note titles and aliases
--tag <tag> Filter by tag (repeat for AND logic)
--property <key=val> Filter by frontmatter property; omit =val to match any note that has the key
--type <type> Filter by resource type: note (any markdown file), attachment, image
--limit <n> Maximum number of results (default: 20)
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

Examples

Search by title:

bash
foam search "meeting"
# notes/team-meeting.md:1: # Team Meeting Notes
# notes/one-on-one-meeting.md:1: # One-on-One Meeting

Filter by tag:

bash
foam search --tag project

Combine a title query with a tag filter:

bash
foam search "alpha" --tag active

Find all notes with a specific frontmatter property:

bash
foam search --property status=draft

Find all notes that have a due property (any value):

bash
foam search --property due

List only daily notes (matches the type set in the note's frontmatter):

bash
foam search --property type=daily-note

User Tools Cli Tag (`docs/user/tools/cli/tag.md`)

foam tag

List, rename, or search tags.

text
foam tag <subcommand> [options]

Subcommands

list

List all tags in the workspace.

bash
foam tag list [options]

This is an alias for foam list tags. Accepts the same filtering and sorting options.

Option Description
--prefix <str> Filter by tag prefix
--sort <count|name> Sort order (default: name)
--limit <n> Maximum number of results

Example:

bash
foam tag list
# #daily          (12 notes)
# #project        (8 notes)
# #project/active (3 notes)

rename

Rename a tag across the entire workspace, including its hierarchical children.

bash
foam tag rename <old> <new>
Option Description
--force Skip confirmation if the rename would merge two existing tags

Example:

bash
foam tag rename todo in-progress
# Renamed: #todo โ†’ #in-progress  (5 notes updated)

Renaming a parent tag also renames its children. Renaming project to work will also rename project/active to work/active.

Find all notes with a given tag.

bash
foam tag search <tag>

This is an alias for foam search --tag <tag>.

Example:

bash
foam tag search project
# notes/alpha.md:1: # Project Alpha
# notes/beta.md:1: # Project Beta

Global options

Option Description
--workspace <dir> Workspace root (default: FOAM_WORKSPACE env var, then current directory)
--format <fmt> Output format: text (default) or json

See also [tags] for tag management in VS Code.


User Tools Cli Update (`docs/user/tools/cli/update.md`)

foam update

Check whether a newer version of foam-cli is available.

text
foam update

The command compares your installed version against the latest release on npm and prints the install command if a newer version exists. It does not install anything for you.

If the npm registry can't be reached, the command falls back to printing the install command without a version comparison.

Options

Option Description
--help Show this help

Examples

Check for updates:

bash
foam update
# Current version: 0.41.0
# Latest version:  0.42.0
#
# To update, run:
#   npm install foam-cli@latest

When already up to date:

bash
foam update
# Current version: 0.42.0
# You are already on the latest version.

--- METRICS ---