Anton Bakulin

Attachments

Contents

Media files referenced with ![[filename]] are served directly from your vault via the /attachments/<path> route. OnyxFolio searches three locations in order when resolving a filename.


Resolution order

When OnyxFolio sees ![[photo.jpg]] in a note at blog/My Post.md, it checks:

  1. <section>/_attachments/photo.jpg — e.g. blog/_attachments/photo.jpg
  2. _attachments/photo.jpg — vault root attachments folder
  3. <ATTACHMENTS_PATH>/photo.jpg — custom path from .env

The first match wins. If no match is found, the embed renders as <em>Missing media: photo.jpg</em>.


Place media files in an _attachments/ subfolder relative to the note's folder:

vault/
  blog/
    My Post.md
    _attachments/
      photo.jpg
      video.mp4
  gallery/
    Photo Gallery.md
    _attachments/
      image1.jpg
      image2.jpg
  _attachments/
    shared-logo.png   ← available to all notes as fallback

Vault root fallback

Files in the vault root _attachments/ are available to all notes regardless of section. Useful for shared assets like logos, icons, or common diagrams.


Custom ATTACHMENTS_PATH

Set ATTACHMENTS_PATH in .env to point to a directory outside the vault:

ATTACHMENTS_PATH=/home/user/shared-media

This is the third and last fallback. Useful when media files live in a separate folder from the vault (e.g. a shared photos directory).


Security

OnyxFolio validates that all resolved file paths stay within the vault directory using os.path.realpath(). Symlinks or path-traversal sequences that escape the vault are rejected and render as missing media. The ATTACHMENTS_PATH fallback is checked separately and not subject to this constraint.


Serving attachments

Attachments are served by Flask's send_from_directory at /attachments/<path>. The path is relative to VAULT_PATH. Direct URL access: /attachments/blog/_attachments/photo.jpg.


See also