Documentation Map
InkStone Documentation
Content & Syntax
Every published note starts with a YAML frontmatter block between --- delimiters. InkStone reads these fields to control publishing, routing, display, and SEO.
Minimal example
---
website: true
title: My Post
date: 2026-04-16
---
That's all you need. Everything else is optional.
Full field reference
---
# ── Publishing ──────────────────────────────────────────
website: true # Required. Omit to keep the note private (not a web page).
type: homepage # Optional: homepage | listing | book
# homepage — renders this note's content at the section root
# listing — auto-generates a post index at the section root
# book — uses the book template with cover/metadata header
# ── Identity ─────────────────────────────────────────────
title: My Post # Overrides the H1 heading and filename. Wrap in quotes if it contains a colon.
slug: my-post # URL slug. Auto-derived from title if omitted.
aliases: # Alternate names that [[wiki-links]] will resolve to this note.
- alternate name
- another alias
# ── Dates ────────────────────────────────────────────────
date: 2026-04-16 # Publication date. Accepted formats: YYYY-MM-DD, DD/MM/YYYY, and more.
updated: 2026-04-20 # Last-modified date. Shown as "Updated …" in post meta and JSON-LD.
# ── Author ───────────────────────────────────────────────
author: "Jane Doe" # String or list. Shown in post meta and JSON-LD.
# author:
# - Jane Doe
# - John Smith
# ── Listing & Discovery ───────────────────────────────────
summary: "..." # Shown on listing cards. Auto-derived from the first ~200 chars if omitted.
featured: true # Highlight in the section's featured area on the listing page.
priority: 0 # Featured posts only. Lower = higher rank. Date breaks ties.
tags: # Content tags. Merged with inline #hashtags from the note body.
- python
- philosophy
# ── Navigation ────────────────────────────────────────────
menu_order: 1 # Pin this note to the top nav. Lower number = further left.
# Appended after auto-generated section links.
# ── Branding (header icon & title) ───────────────────────
icon: _attachments/logo.png # Image shown beside the site title in the header.
# Accepts a vault-relative path, /static/... URL, or full URL.
# Cascades to all child pages unless overridden lower down.
site_title: "My Brand" # Replaces the website name displayed in the header.
# Also cascades to child pages.
# ── Banner image ─────────────────────────────────────────
banner: "https://example.com/image.jpg"
banner_x: 0.5 # Horizontal focal point, 0–1 (default: centre).
banner_y: 0.4 # Vertical focal point, 0–1 (default: centre).
# ── Root homepage only ───────────────────────────────────
show_search: true # Adds a Search link to the top nav.
show_tags: true # Adds a Tags link to the top nav.
default_theme: dark # Initial theme for new visitors: "dark", "light", or "system" (default).
# "system" follows the OS prefers-color-scheme. Visitors can always
# override with the toggle; their choice is stored in localStorage.
# ── Multilingual ─────────────────────────────────────────────
language: en # Root homepage only. Sets the default language for the site (e.g. "en", "ru").
lang: ru # Per-note language code. Overrides filename suffix if both are present.
# Filename suffix _RU.md is equivalent to setting lang: ru in frontmatter.
# Social links — one key per platform. InkStone extracts the handle from the
# URL and renders [icon] @handle in the footer. Supported keys:
# github, mastodon, bluesky, twitter, instagram, linkedin, facebook, youtube
github: https://github.com/you
mastodon: https://mastodon.social/@you
bluesky: https://bsky.app/profile/you.bsky.social
---
Title resolution order
When no title is set in frontmatter, InkStone falls back in this order:
- Frontmatter
title - First
# H1heading in the note body - Filename (without
.md)
The YAML colon rule
Any string value that contains a colon (:) must be wrapped in double quotes, otherwise YAML silently parses it as a nested mapping and the field breaks.
title: "From Vault to Web: How This Works" # correct
title: From Vault to Web: How This Works # broken — YAML sees a nested dict
InkStone logs a WARNING to stderr and falls back to H1/filename when it detects a dict-valued title.
To embed literal " characters in a title, wrap the whole value in single quotes:
title: '"Hello World" Considered Harmful' # → "Hello World" Considered Harmful
Accepted date formats
InkStone parses date and updated in any of these formats:
| Format | Example |
|---|---|
YYYY-MM-DD |
2026-04-16 |
YYYY-MM-DD HH:MM |
2026-04-16 09:30 |
YYYY-MM-DD HH:MM:SS |
2026-04-16 09:30:00 |
YYYY/MM/DD |
2026/04/16 |
DD-MM-YYYY |
16-04-2026 |
DD/MM/YYYY |
16/04/2026 |
Slug and URL generation
slugis auto-generated fromtitleif omitted: lowercased, spaces → hyphens, non-alphanumerics stripped.- The URL is
/<section>/<slug>for posts in subfolders, or/<slug>for vault-root notes. aliasesregister additional wiki-link names that all resolve to the same URL.
See also
- Post Types — how
type: homepage,type: listing, andtype: bookwork - Publishing and Privacy — the
website: trueflag and private notes - Navigation —
menu_orderand nav pinning - SEO and Metadata — banner images, author, OpenGraph
- Branding — favicon override, site icon, and header title cascade
- Comments — Giscus comment system setup
- Social Links — social footer icons, supported platforms, handle extraction
- Multilingual — filename suffix routing, language toggle, UI string translations
InkStone resolves [[wiki-links]] server-side during the two-pass vault load. All standard Obsidian link forms are supported.
Basic link
[[Note Title]]
Renders as a link to the note titled "Note Title". Display text defaults to the note title.
Alias / custom display text
[[Note Title|Display Text]]
The part after | becomes the link text. The target is still resolved by title.
Spaces around the | are allowed — both forms are equivalent:
[[Note Title|Display Text]]
[[Note Title | Display Text]]
Heading anchor
[[Note Title#Section Heading]]
Links to a specific heading within the target note. The anchor is slugified (lowercased, spaces → hyphens).
Display text defaults to Note Title › Section Heading. Override with |:
[[Note Title#Section Heading|Read this section]]
Block reference
[[Note Title^block-id]]
Links to a specific block (paragraph) tagged with ^block-id in the target note. See Obsidian Syntax for how to create block IDs.
[[Note Title^block-id|Custom label]]
Resolution order
InkStone builds a URL index during Pass 1 of the vault load. Each note is indexed under three keys:
slugify(title)— matches[[Note Title]]slugify(filename without .md)— matches by filename even when title differs- frontmatter
slug— matches the slug directly aliases— each alias is indexed as a separate key
This means [[Filename|Display]], [[Title]], and [[slug]] all resolve correctly even when the three differ.
If a target is not found in the index, InkStone falls back to /<slugified-title> — the link is still generated, it just may 404.
Cross-section links
Wiki-links resolve correctly across sections. A link from blog/My Post.md to gallery/Photo.md generates /gallery/photo — no need to include the section prefix.
Section homepage links
Links to notes registered as type: homepage or type: listing resolve to the section URL, not the file's computed slug URL. For example, [[Blog]] resolves to /blog, not /blog/blog.
Aliases
Add aliases: to a note's frontmatter to register additional link names:
aliases:
- my alternate name
- another alias
Any of these names can then be used in [[wiki-links]] from other notes.
See also
- Frontmatter Reference —
aliasesfield - Note Transclusion —
![[Note Title]]for embedding note content - Obsidian Syntax — block IDs (
^block-id)
Callouts are styled highlight boxes rendered from Obsidian's > [!type] blockquote syntax. Everything is processed server-side — no client-side plugins needed.
Basic callout
> [!note] Title text
> Body content here.
> More body content.
Body content here. More body content.
Collapsible callout
Add - after the closing ] to make the callout collapsed by default (rendered as a closed <details> element):
> [!warning]- Click to expand
> Hidden content here.
Click to expand
Hidden content here.
Add + to pin it open (rendered as an open <details> element — user can still collapse):
> [!tip]+ Always visible
> This starts open but can be collapsed.
Always visible
This starts open but can be collapsed.
Callout types
InkStone supports the full set of Obsidian callout types. Each gets a distinct icon and accent colour:
General annotations
Helpful hints
Informational content
Cautions and caveats
Critical warnings
Positive outcomes
Questions and FAQs
Quoted text
High-priority notes
Summaries and abstracts
Action items
Code or usage examples
Known issues
Any custom type not in the list above is still rendered — it just uses the default styling.
Omitting the title
Leave the title empty to render the callout type name as the title:
> [!tip]
> Body content here.
Body content here.
Multi-paragraph body
Each > line in the blockquote is part of the callout body. Standard markdown (bold, code, links) renders inside the body.
> [!example] Code example
> Here is a usage pattern:
>
> ```python
> print("hello")
> ```
>
> And an explanation.
Here is a usage pattern:
print("hello")
And an explanation.
CSS customisation
Callout styles live in frontend/static/callouts.css (default Catppuccin-inspired theme) and frontend/static/omarchy-callouts.css (Omarchy theme). Override these files to change colours, icons, or layout.
Each callout renders with the class callout callout-{type}, so per-type targeting is straightforward:
.callout-warning { border-left-color: #f5a623; }
See also
- Themes — switching between CSS theme files
- Obsidian Syntax — other Obsidian-native syntax features
InkStone converts ![[filename]] embed syntax into lightbox images, image sliders, video players, and audio players. All media is served directly from your vault.
Where to put files
Media files go in an _attachments/ subfolder relative to the note's folder:
| Note location | Attachments folder |
|---|---|
blog/My Post.md |
blog/_attachments/ |
gallery/Photo.md |
gallery/_attachments/ |
Root Note.md |
_attachments/ |
If the file isn't found in the section's _attachments/, InkStone falls back to the vault-root _attachments/, then to ATTACHMENTS_PATH from .env. See Attachments for the full resolution order.
Single image
![[photo.jpg]]
Renders as a lightbox image. Click to open full-screen.

With caption
![[photo.jpg|Caption text here]]
Renders with a <figcaption> below the image.

With fixed width (pixels)
![[photo.jpg|600]]
A numeric-only value is treated as a pixel width, not a caption. The image is constrained to that width.

Inline illustration
Use the inline modifier to place an image in the text flow without a lightbox. The image appears as a centered figure and does not open when clicked.
![[photo.jpg|inline]]
Combine modifiers in order: inline, then optional pixel width, then optional caption text — all in one pipe argument:
| Syntax | Result |
|---|---|
![[photo.jpg\|inline]] |
Centered figure, no lightbox |
![[photo.jpg\|inline 400]] |
Inline, max-width 400 px |
![[photo.jpg\|inline A mountain trail]] |
Inline with caption |
![[photo.jpg\|inline 400 A mountain trail]] |
Inline, width + caption |
The width must be a plain integer immediately after inline. Anything after the number is treated as caption text.
Image gallery (lightbox)
Place multiple embeds on separate lines to create a thumbnail gallery. Clicking any image opens the lightbox viewer:
![[photo1.jpg]]
![[photo2.jpg]]
![[photo3.jpg]]



Image slider
Place multiple embeds on a single line (space-separated) to create a swipeable slider:
![[photo1.jpg]] ![[photo2.jpg]] ![[photo3.jpg]]
Renders with left/right arrows and dot navigation.
Video
![[video.mp4]]
Supported formats: .mp4, .webm, .mov. Renders as an HTML5 <video> element with controls.
Audio
![[track.mp3]]
Supported formats: .mp3, .ogg, .wav, .flac, .m4a. Renders as an HTML5 <audio> element with controls.
Missing media
If the file cannot be found in any of the attachment locations, InkStone renders:
<em>Missing media: filename.jpg</em>
No broken image icons. Check the attachment folder structure if this appears.
Security note
InkStone validates that resolved file paths stay within the vault directory. Symlinks or paths that escape the vault are rejected and render as missing.
See also
- Attachments — resolution order and fallback paths
- Note Transclusion — embedding other notes inline (not media files)
Transclusion lets you embed the content of one note inside another. InkStone resolves ![[Note Title]] embeds server-side — the target note's body is rendered inline, wrapped in a styled container.
Full note transclusion
![[Note Title]]
Embeds the full body of the target note (frontmatter stripped, H1 stripped). The content is rendered as HTML and wrapped in a <div class="transclusion"> block with a link back to the source.
Section transclusion
![[Note Title#Heading]]
Embeds only the content under a specific heading — from that heading down to the next heading of equal or higher level.
If the heading is not found, the full note body is embedded as a fallback.
Aliases in transclusion
![[Note Title#Heading|Optional alias]]
The alias is silently ignored in transclusion (it's used in wiki-links). The transclusion title shown in the embed header is always derived from the target note and heading.
How it looks
The transcluded content renders as a distinct inset block:
- Title bar — shows the note title (or "Title › Heading" for section transclusion), linked to the original note
- Body — the target note's markdown rendered to HTML, including callouts, checkboxes, and highlights
How it works
Transclusion renders these sub-features inside the embedded body:
- Callouts
- Checkboxes
- Highlights (
==text==) - Standard markdown (bold, italic, tables, code)
Nested transclusion (a transcluded note that itself contains ![[...]]) is not recursively resolved — the inner embeds are left as-is.
Target note requirements
- The target note does not need
website: trueto be transcludable — private notes can be transcluded. - The note is looked up by title, filename stem, or slug (same resolution as Wiki-Links).
- If the target is not found, a
<em class="transclusion-missing">Note not found: Title</em>placeholder is rendered instead.
See also
- Wiki-Links —
[[Note Title]]for linking (not embedding) - Media Embeds —
![[image.jpg]]for media files - Publishing and Privacy — notes without
website: trueare private but still transcludable
InkStone renders these Obsidian-specific inline syntax elements server-side. No client-side plugin or JavaScript is needed.
Checkboxes
Standard Obsidian task lists with nested indentation support.
- [ ] Unchecked item
- [x] Checked item
- [ ] Nested unchecked
- [x] Nested checked
- Unchecked item
- Checked item
- Nested unchecked
- Nested checked
Renders as HTML <input type="checkbox" disabled> elements in a <ul class="checkbox-list">. Checkboxes are non-interactive (read-only display).
Nesting is based on 4-space (or tab) indentation. Multiple levels are supported.
Highlights
This is ==highlighted text== in a sentence.
This is highlighted text in a sentence.
Converts to <mark>highlighted text</mark>. Works anywhere in the body — inline with other text.
Highlight syntax inside backtick code spans is left untouched:
Use `==this==` syntax in your notes. ← the == here is NOT converted
Use
==this==syntax in your notes. ← the == here is NOT converted
Footnotes
Standard markdown footnote syntax, processed by the footnotes extension:
Here is a claim.[^1]
[^1]: This is the footnote content.
Here is a claim.1
Footnotes are collected and rendered at the bottom of the post as a numbered list with back-links.
Block IDs
Append ^block-id at the end of a paragraph to create a named anchor target:
This is an important paragraph. ^my-anchor
Renders as:
This is an important paragraph. <span id="my-anchor"></span>
This is an important paragraph.
Other notes can then link to this block:
[[Note Title^my-anchor]]
[[Note Title^my-anchor|Jump to this section]]
Block IDs may contain letters, numbers, hyphens, and underscores. The anchor ID is lowercased.
Standard Markdown
InkStone also runs the full standard markdown pipeline via Python-Markdown with these extensions enabled:
| Extension | What it adds |
|---|---|
fenced_code |
```lang ``` code blocks |
tables |
GFM-style pipe tables |
toc |
Auto-generated table of contents anchors |
md_in_html |
Markdown inside raw HTML blocks |
codehilite |
Syntax highlighting in code blocks |
footnotes |
[^1] footnote syntax |
See also
- Callouts —
> [!type]callout boxes - Wiki-Links —
[[Note]]internal links - Math and Diagrams —
$inline$and$$block$$math - Code Blocks — syntax highlighting detail
-
This is the footnote content. ↩
InkStone supports two diagram/formula systems: KaTeX for mathematical notation and Mermaid for flowcharts and diagrams. Both are rendered client-side in the browser.
LaTeX / KaTeX
Math expressions are written in standard LaTeX syntax and rendered by KaTeX.
Inline math
Wrap expressions in single $ delimiters:
The formula is $E = mc^2$ inline.
Renders as: The formula is E = mc^2 inline.
Block math
Wrap expressions in double $$ delimiters:
$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$
Renders as:
How it works
InkStone protects math expressions from the markdown parser before standard rendering:
$$...$$→<div class="math-block">...</div>$...$→<span class="math-inline">...</span>
The KaTeX library (loaded in base.html) then renders these elements in the browser. Expressions inside backtick code spans are left untouched.
KaTeX limitations
KaTeX supports most common LaTeX math commands. It does not support every package or macro from full LaTeX/MathJax. See the KaTeX support table for the full list of supported functions.
Mermaid diagrams
Fenced code blocks with the mermaid language tag are rendered as diagrams:
```mermaid
graph LR
A[Start] --> B{Decision}
B -->|Yes| C[Do this]
B -->|No| D[Do that]
```
Supported diagram types
Mermaid supports a wide range of diagram types:
| Type | Syntax keyword |
|---|---|
| Flowchart | graph LR / graph TD |
| Sequence diagram | sequenceDiagram |
| Class diagram | classDiagram |
| State diagram | stateDiagram-v2 |
| Entity–relationship | erDiagram |
| Gantt chart | gantt |
| Pie chart | pie |
| Gitgraph | gitGraph |
Theme adaptation
Mermaid diagrams automatically adapt to the active dark or light theme. When the user toggles the theme, the diagrams re-render with matching colours.
How it works
Mermaid is loaded as a client-side library in base.html. Fenced mermaid blocks are passed through the markdown pipeline as literal <code class="language-mermaid"> blocks, which Mermaid's initializer then finds and renders into SVG.
See also
- Code Blocks — syntax-highlighted code blocks
- Themes — dark/light mode toggle
Publishing & Structure
The type: frontmatter field controls how InkStone registers and renders a note. There are four types.
Regular post (default)
No type: field (or any unrecognised value):
---
website: true
title: My Post
date: 2026-04-16
---
- Served at
/<section>/<slug>or/<slug>for vault-root notes. - Appears in section listings, search, and RSS feeds.
- Uses the
post.htmltemplate.
type: homepage
type: homepage
- Renders this note's own markdown content at the section root URL.
- Root note (
/) — the site homepage, usesindex.html. - Subfolder note (
blog/) — the section homepage at/blog, usesindex.html. - Does not appear in listings, search, or RSS.
- The root homepage's
titlebecomes the globalWEBSITE_NAMEshown in the browser title and nav. - Root-only fields:
show_search,show_tags.
Each section can have at most one type: homepage. If multiple files in the same folder declare type: homepage, the last one loaded wins (undefined behaviour — avoid).
type: listing
type: listing
- Auto-generates a paginated post index at the section root URL.
- Lists all posts in the section (and sub-sections), sorted by date.
- Featured posts (with
featured: true) appear in a separate highlighted area at the top. - Does not appear in listings, search, or RSS.
- Uses the
listing.htmltemplate. - Intro content from the listing note's own body (if any) is shown above the post list.
Each section can have at most one type: listing.
Auto-listing fallback
If a section has posts but no explicit listing or homepage file, InkStone automatically creates a minimal listing route for that section. The title is derived from the folder name.
type: book
type: book
- A specialised post type for long-form content with a cover image and metadata header.
- Uses the
book.htmltemplate instead ofpost.html. - The first
<img>paragraph in the body is extracted and rendered as a cover image in the template header — it does not appear in the body. - Appears in listings, search, and RSS like a regular post.
Typical use: book reviews, long essays, or any note where a prominent cover image + metadata header makes sense.
Choosing between listing and homepage
| Need | Use |
|---|---|
| Auto-generated post index for a section | type: listing |
| Custom intro page for a section with your own content | type: homepage |
| Site root with custom content | type: homepage at vault root |
| Book/long-form post with cover image | type: book |
| Regular post | omit type |
See also
- Navigation — how section roots appear in the nav bar
- Pagination — how listing pages paginate
- Publishing and Privacy —
website: trueand private notes - SEO and Metadata — how
type: bookuses JSON-LD Book schema
InkStone gives you fine-grained control over which notes appear on the public web. The opt-in model means everything is private by default.
Publishing a note
Add website: true to any note's frontmatter to publish it:
---
website: true
title: My Post
date: 2026-04-16
---
Without this field (or with website: false), the note is not accessible as a web page.
Private notes
A note without website: true is private:
- It does not appear on listing pages or search results.
- Its URL returns a "this note is private" placeholder page (not a 404).
- It is fully accessible to Dataview queries — you can reference private notes in tables and lists.
- It can be transcluded into published notes using
![[Note Title]]. - Its tags are collected and usable in Dataview
FROM #tagqueries.
This makes private notes useful as: - Drafts (visible in Dataview listings but not published) - Metadata records (referenced by other notes) - Data sources for dynamic tables
Private page placeholder
When a visitor navigates to a private note's URL, InkStone renders a private.html template instead of a 404. The page shows the note title and a back link to the parent section. This prevents broken wiki-links when you reference a private note from a published one.
Homepage and listing files
Notes with type: homepage or type: listing are registered in SECTION_ROUTES and do not appear in ALL_POSTS. They are never listed in section listings or search results — they serve as the section root page only.
Unlisted root-level notes
Notes in the vault root (no subfolder) that do not have type: homepage or type: listing are served at /<slug> with no section prefix. They do not appear in any auto-generated listing.
The intended way to surface them:
- Link to them via [[wiki-links]] from other content
- Pin them to the nav via menu_order
Aliases
Published notes can register alternate link names via the aliases frontmatter field. All aliases resolve to the same URL — useful when a note is known by multiple names across your vault.
aliases:
- quick reference
- cheat sheet
See also
- Frontmatter Reference —
website,type,aliases - Post Types — homepage, listing, book
- Dataview — querying private notes
- Note Transclusion — embedding private notes
InkStone builds all site navigation automatically from vault structure and frontmatter. No config files or menus to maintain.
Top navigation bar
Auto-generated section links
Every top-level vault folder that has a type: listing or type: homepage note appears as a nav link. The link text is the section title; the URL is the section root.
Example vault structure:
blog/
Blog Index.md ← type: listing → nav link "Blog" → /blog
gallery/
Gallery.md ← type: homepage → nav link "Gallery" → /gallery
The order of auto-generated section links is alphabetical by URL.
Pinned nav items
Any note with menu_order: N in its frontmatter is appended to the nav after the auto-generated section links:
menu_order: 1 # lower = further left
Multiple notes can be pinned. They are sorted by menu_order value (ascending). This is the intended mechanism for standalone pages like About or Contact.
Search and Tags links
Opt-in nav links controlled by root homepage frontmatter:
show_search: true # adds "Search" link
show_tags: true # adds "Tags" link
Breadcrumb trail
Every post page shows a breadcrumb trail: Home › Section › Post.
- Each segment links to its URL if that URL has a section route.
- The final segment (current post) is plain text — no link.
- Breadcrumbs are built from the URL path, not the folder structure directly.
Previous / next post navigation
Each post page shows ← Older and Newer → links at the bottom, navigating to the adjacent post within the same section, ordered by date.
- Posts without a date are excluded from prev/next ordering.
- Adjacent posts are computed within the same section — cross-section navigation is not included.
Related posts
Below each post, InkStone shows up to 4 related posts. The relevance score is:
score = (shared tags × 2) + (same section ? 1 : 0)
Ties are broken by date (newest first). Posts with no shared tags and no section match are not shown.
Related posts are pre-computed at vault load time — O(n²) once, O(1) per request.
Mobile navigation
On narrow viewports, the nav wraps below the site title into a compact row. No hamburger — all links remain visible and tappable. The theme toggle is pinned to the top-right corner.
See also
- Post Types — how
type: listingandtype: homepagecreate section roots - Frontmatter Reference —
menu_order,show_search,show_tags - Tags — the
/tagsindex page - Search — the
/searchpage
InkStone collects tags from two sources per note: the tags: frontmatter list and inline #hashtag mentions in the note body. Both are merged into a single tag set.
Frontmatter tags
tags:
- python
- philosophy
- open-source
Tags are lowercased and normalised at load time.
Inline hashtags
Any #word in the note body that starts with a letter is collected as a tag:
This post is about #python and #obsidian workflows.
These are merged with frontmatter tags — no duplicates.
Pattern: #([A-Za-z][A-Za-z0-9_-]*) — must start with a letter, may contain letters, numbers, hyphens, and underscores.
Tag display
On post pages, tags render as clickable badge links. Clicking a tag navigates to its archive page.
Tag archive pages
Each tag has its own archive page at /tag/<name>:
/tag/python — all posts tagged "python"
/tag/philosophy — all posts tagged "philosophy"
Archive pages show posts sorted by date (newest first) with prev/next navigation within the tag.
Tag index
Enable the /tags page by adding show_tags: true to the root homepage's frontmatter:
show_tags: true
This adds a Tags link to the top navigation. The tags index at /tags lists every tag with its post count, sorted alphabetically.
Search filter
The search page at /search includes a tag filter dropdown. Users can narrow results to a specific tag while also entering a text query.
Dataview filtering
Use tags in FROM and WHERE clauses to query notes by tag:
```dataview
TABLE date, summary
FROM #inkstone
SORT date DESC
LIMIT 3
```
| File | date | summary |
|---|---|---|
| Obsidian Bases | 2026-04-30 | Publish .base database views as filtered, sorted HTML tables — filename markers, filter syntax, and available fields. |
| Media Embeds | 2026-04-24 | Images, video, and audio embeds — lightbox, sliders, captions, inline illustrations, and width control. |
| Themes | 2026-04-24 | Dark, light, and system mode toggle, CSS theme architecture, and how to select or create a theme. |
```dataview
LIST
FROM #documentation
SORT date DESC
LIMIT 5
```
Tags on private notes
Tags are collected from all vault notes during Pass 1, including notes without website: true. This means private notes contribute to Dataview tag queries even though they don't appear on the public site.
See also
- Search — text search with tag filter
- Dataview —
FROM #tagandcontains(tags, ...)queries - Frontmatter Reference —
show_tagsandtagsfields
Listing pages automatically paginate regular posts when there are more than 20. Featured posts are not paginated — they always appear at the top.
How it works
- Page size: 20 regular posts per page.
- Featured posts: shown in full above the pagination — never paginated regardless of count.
- Sort order: newest first (by
date). Posts without a date appear after dated posts. - URL parameter:
?page=N— page numbers start at 1.
Example URLs:
/blog → page 1 (default)
/blog?page=2 → page 2
/blog?page=3 → page 3
Out-of-range pages
If ?page=N exceeds the total number of pages, InkStone clamps the value to the last valid page. No 404 for out-of-range page numbers.
Listing template
The listing.html template renders:
- Featured posts — horizontally highlighted cards (all, no pagination)
- Regular posts — cards, 20 per page
- Pagination controls — prev/next page links with current page indicator
Section scope
Pagination covers posts in the section and all sub-sections. A listing at /blog paginates both blog/*.md and blog/series/*.md posts together.
See also
- Post Types —
type: listingcreates the listing page - Navigation — how section listing pages appear in the nav
Media files referenced with ![[filename]] are served directly from your vault via the /attachments/<path> route. InkStone searches three locations in order when resolving a filename.
Resolution order
When InkStone sees ![[photo.jpg]] in a note at blog/My Post.md, it checks:
<section>/_attachments/photo.jpg— e.g.blog/_attachments/photo.jpg_attachments/photo.jpg— vault root attachments folder<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>.
Recommended structure
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
InkStone 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
- Media Embeds —
![[file]]syntax for images, video, audio - Getting Started — setting
VAULT_PATHin.env
InkStone has two independent translation mechanisms: content translation for publishing notes in multiple languages, and UI string translation for localising all fixed labels in templates.
Content Translation — _LANG filename suffix
Add a two-letter ISO language code as a suffix to any note filename to mark it as a translation:
blog/My Post.md → /blog/my-post (default language)
blog/My Post_RU.md → /blog/my-post/ru
blog/My Post_FR.md → /blog/my-post/fr
blog/My Post_DE.md → /blog/my-post/de
The suffix is case-insensitive. You can also set the language explicitly in frontmatter instead of (or in addition to) the suffix:
---
website: true
lang: ru
title: Мой Пост
---
Setting the default language
The site's default language is read from language: in the root homepage's frontmatter:
---
website: true
type: homepage
language: en
---
If omitted, en is used. Notes without a language suffix are treated as the default language.
What the engine does automatically
- A language toggle appears in the site header when the current page has translations available.
- Navigating to a URL whose language variant doesn't exist auto-redirects to the default language and shows a "not yet translated" banner.
hreflangmeta tags are injected on all pages for SEO.- Section homepages and listings can also be translated with the same suffix convention.
UI String Translations — type: translations note
Every fixed string baked into the templates can be translated without editing any HTML. Create a vault note with two parts.
Frontmatter — just the type and language code:
---
type: translations
lang: ru
---
Note body — a fenced yaml block with the string pairs:
```yaml
Search: Поиск
Tags: Теги
"All tags": Все теги
"No results": Нет результатов
for: для
result: Результат
results: Результаты
tagged: с тегом
"min read": мин чтения
Featured: Избранное
"All Posts": Все Посты
"No posts yet.": Пока нет записей.
"No tags yet.": Пока нет тегов.
"built with": создано с помощью
Home: Главная
Contents: Содержание
"See also": Смотрите также
Updated: Обновлено
by: автор
date_format: "{day} {month} {year}"
January: января
February: февраля
March: марта
April: апреля
May: мая
June: июня
July: июля
August: августа
September: сентября
October: октября
November: ноября
December: декабря
"Not yet translated": Ещё не переведено
"Translation unavailable": Перевод недоступен
"This page is not yet available in": Эта страница ещё не доступна на языке
"Read it in": Читать на
```
The strings live in the note body rather than nested in frontmatter — much easier to edit in Obsidian's editor.
Translation notes are loaded automatically regardless of website: status. Place them anywhere in the vault.
Rules:
- One note per language. Two notes with the same lang: log a warning; the last one loaded wins.
- Only keys that exist in the block are translated. Missing keys fall back to the English default.
- Strings that contain a colon must be quoted (standard YAML rule).
Date localisation
Dates are rendered using date_format and individual month name keys. The default format is {month} {day}, {year} (e.g. "April 25, 2026"). To use a different order or separator, set date_format and translate each month name:
date_format: "{day} {month} {year}"
January: января
February: февраля
# … all 12 months
The placeholders {day}, {month}, and {year} are always available. Month names are looked up by their English name (January, February, …) so you only need to include the languages you're adding.
Search and Tags link language context
The Search and Tags nav links automatically append ?lang=<code> when the visitor is on a non-default language page, so /search and /tags load with the correct UI language. The search form also preserves the language across submissions.
Translating section index pages
Section listing pages (type: listing) and homepage files (type: homepage) can also be translated:
blog/Blog Index.md → /blog (default)
blog/Blog Index_RU.md → /blog/ru
The translated listing page will show only posts that have a _RU variant.
Example vault structure
My Vault/
homepage.md → /
homepage_RU.md → /ru
_UI Translations_RU.md → (loaded for UI labels, no URL)
blog/
Blog Index.md → /blog
Blog Index_RU.md → /blog/ru
My Post.md → /blog/my-post
My Post_RU.md → /blog/my-post/ru
InkStone reads frontmatter to determine how a note is published. Two plugins handle auto-generating that frontmatter when you create a new note: QuickAdd (recommended) and Obsidian's built-in Templates plugin.
The demo vault ships with five ready-to-use templates in the templates/ folder, one for each page type.
Available templates
| QuickAdd command | Template file | Page type |
|---|---|---|
| New Post | templates/web page template.md |
Regular blog post or standalone page |
| New Homepage | templates/homepage template.md |
Section root with custom content (type: homepage) |
| New Listing Page | templates/listing page template.md |
Auto-generated post index (type: listing) |
| New Book | templates/book template.md |
Book entry with cover/author layout (type: book) |
| New Translations Note | templates/translations template.md |
UI label overrides for a language (type: translations) |
Recommended: QuickAdd
QuickAdd is a community plugin that prompts you for values when creating a note, then fills the frontmatter automatically.
Setup (already done in the demo vault):
1. Install QuickAdd from Community Plugins.
2. Each template above is already registered as a command — no extra configuration needed.
3. All five commands are available via Ctrl+P.
Usage:
1. Press Ctrl+P and type the command name (e.g. New Post).
2. QuickAdd prompts for the required fields (title, summary, author — depending on the template).
3. Choose which folder to place the note in.
4. A new note opens with the date filled in and all frontmatter ready.
{{VALUE:Label}} prompts for user input. {{DATE:format}} inserts today's date. Both are QuickAdd syntax — they do nothing in a plain text editor.
Template contents
New Post
---
website: true
title: {{VALUE:Title}}
date: {{DATE:YYYY-MM-DD}}
summary: "{{VALUE:Summary}}"
tags:
-
---
New Homepage
---
website: true
type: homepage
title: {{VALUE:Title}}
date: {{DATE:YYYY-MM-DD}}
language: en
show_search: false
show_tags: false
---
New Listing Page
---
website: true
type: listing
title: {{VALUE:Title}}
date: {{DATE:YYYY-MM-DD}}
summary: "{{VALUE:Summary}}"
---
New Book
---
website: true
type: book
title: {{VALUE:Title}}
date: {{DATE:YYYY-MM-DD}}
author: "{{VALUE:Author}}"
summary: "{{VALUE:Summary}}"
tags:
-
---
New Translations Note
Frontmatter:
---
type: translations
lang: {{VALUE:Language code (e.g. ru, fr, de)}}
---
Note body (a fenced yaml block):
Search:
Tags:
"All tags":
"No results":
for:
result:
results:
tagged:
"min read":
"Not yet translated":
"Translation unavailable":
"This page is not yet available in":
"Read it in":
Featured:
"All Posts":
They are loaded automatically regardless of that field. The strings live in the note body, not in frontmatter — much easier to edit in Obsidian.
Simpler alternative: Core Templates
Obsidian's built-in Templates plugin inserts a template file into the current note. It supports {{date}} and {{title}} (the file name) but cannot prompt for other values.
Setup:
1. Enable Templates in Settings → Core plugins.
2. Set the Template folder location to templates/.
3. Open a new note, then run Templates: Insert template from the command palette and pick the template you want.
Use this if you prefer no prompts and are happy to fill fields manually.
Optional frontmatter fields
The templates only include the fields each type always needs. Add these manually when required:
| Field | When to add |
|---|---|
featured: true |
Highlight on the section listing page |
updated: YYYY-MM-DD |
Show "Updated …" separately from date |
slug: custom-slug |
Override the auto-generated URL slug |
lang: ru |
Mark as a translation variant |
banner: "url" |
Hero image at the top of the post |
banner_x / banner_y |
Focal point for the banner image (0–1) |
menu_order: 1 |
Pin to the top navigation bar |
priority: 0 |
Sort order among featured posts |
See Frontmatter Reference for the full list.
Customising templates
Edit any file in templates/ directly in Obsidian. To add an author prompt to the post template:
author: {{VALUE:Author (leave blank to skip)}}
QuickAdd's full template syntax: github.com/chhoumann/quickadd.
Discovery & Search
InkStone provides full-text search at /search. Search is server-side — no index file or JavaScript search library needed.
Enabling search
Add show_search: true to the root homepage's frontmatter to add a Search link to the top navigation:
---
website: true
type: homepage
show_search: true
---
Without this flag, /search still works — it just isn't linked in the nav.
How to search
Navigate to /search and enter a query. Results update on form submission.
URL parameters:
| Parameter | Description |
|---|---|
q |
Text query string |
tag |
Tag filter (exact match, case-insensitive) |
Example: /search?q=python&tag=tutorial
What is searched
The search checks two fields per post:
- Title — case-insensitive substring match
- Content — the full rendered post text (HTML tags stripped, lowercased)
A post matches if the query appears in either field.
Tag filter
The search page shows a dropdown of all tags across published posts. Selecting a tag narrows results to posts with that tag in addition to the text query. Both filters apply simultaneously.
The tag list is pre-computed at vault load time from all published posts.
Query highlighting
Matched query terms are highlighted in the result list:
- Title — matched terms wrapped in
<mark>tags - Summary — matched terms wrapped in
<mark>tags
Search scope
Search covers only published posts (website: true). Private notes, homepage files, and listing files are excluded.
See also
- Tags — tag archive pages and the
/tagsindex - Frontmatter Reference —
show_searchfield
InkStone implements a server-side Dataview engine that executes TABLE and LIST queries from fenced ```dataview ``` blocks. Queries run at vault-load time — no client-side plugin needed.
This feature mirrors the Obsidian Dataview plugin. Writing in Obsidian means you can preview queries live before publishing.
TABLE query
```dataview
TABLE date, summary
FROM #documentation
SORT date DESC
LIMIT 3
```
| File | date | summary |
|---|---|---|
| Obsidian Bases | 2026-04-30 | Publish .base database views as filtered, sorted HTML tables — filename markers, filter syntax, and available fields. |
| Media Embeds | 2026-04-24 | Images, video, and audio embeds — lightbox, sliders, captions, inline illustrations, and width control. |
| Themes | 2026-04-24 | Dark, light, and system mode toggle, CSS theme architecture, and how to select or create a theme. |
Each row in the result corresponds to a vault note. The first column is always the note title (as a link) unless WITHOUT ID is used.
Column aliases
```dataview
TABLE date as "Published", summary as "Description"
FROM #inkstone
SORT date DESC
LIMIT 3
```
| File | "Published" | "Description" |
|---|---|---|
| Obsidian Bases | 2026-04-30 | Publish .base database views as filtered, sorted HTML tables — filename markers, filter syntax, and available fields. |
| Media Embeds | 2026-04-24 | Images, video, and audio embeds — lightbox, sliders, captions, inline illustrations, and width control. |
| Themes | 2026-04-24 | Dark, light, and system mode toggle, CSS theme architecture, and how to select or create a theme. |
The as "Label" syntax renames a column header.
WITHOUT ID
```dataview
TABLE WITHOUT ID title, date
FROM #documentation
SORT date ASC
LIMIT 3
```
| title | date |
|---|---|
| Deployment | 2026-04-15 |
| Features | 2026-04-15 |
| Getting Started | 2026-04-15 |
Suppresses the default "File" (title/link) first column.
LIST query
```dataview
LIST
FROM #inkstone
SORT date DESC
LIMIT 5
```
Renders as a <ul> of linked note titles.
LIST with extra field
```dataview
LIST summary
FROM #documentation
SORT date DESC
LIMIT 3
```
- Obsidian Bases — Publish .base database views as filtered, sorted HTML tables — filename markers, filter syntax, and available fields.
- Media Embeds — Images, video, and audio embeds — lightbox, sliders, captions, inline illustrations, and width control.
- Themes — Dark, light, and system mode toggle, CSS theme architecture, and how to select or create a theme.
Appends — summary after each link.
FROM clause
Filter the source notes:
| FROM syntax | What it selects |
|---|---|
FROM #tag |
Notes with that tag |
FROM /folder |
Notes in that folder (not yet folder-based — use tag filtering) |
FROM currently supports tag-based filtering (#tag). Folder-based filtering is not yet implemented.
WHERE clause
Filter by any frontmatter field or computed property:
```dataview
TABLE date
FROM #inkstone
WHERE contains(tags, "documentation")
SORT date DESC
LIMIT 3
```
| File | date |
|---|---|
| Obsidian Bases | 2026-04-30 |
| Media Embeds | 2026-04-24 |
| Themes | 2026-04-24 |
Supported operators
| Operator | Syntax | Example |
|---|---|---|
| AND | & |
featured = true & date > "2026-01-01" |
| OR | \| |
featured = true \| priority = 0 |
| contains | contains(field, "value") |
contains(tags, "inkstone") |
| not contains | !contains(field, "value") |
!contains(tags, "draft") |
SORT clause
Multiple sort keys (comma-separated, applied right-to-left):
```dataview
TABLE date
FROM #documentation
SORT date DESC
LIMIT 3
```
| File | date |
|---|---|
| Obsidian Bases | 2026-04-30 |
| Media Embeds | 2026-04-24 |
| Themes | 2026-04-24 |
LIMIT clause
```dataview
LIST
FROM #inkstone
SORT date DESC
LIMIT 5
```
Caps the result at N rows after sorting. Always add LIMIT when the result set could be large.
GROUP BY
Flattened groups (sub-table per group)
```dataview
TABLE date, summary
FROM #inkstone
GROUP BY file.folder
```
inkstone
| File | date | summary |
|---|---|---|
| InkStone | 2026-04-15 | A lightweight Python/Flask engine that turns a Markdown vault into a website — no export step, no build pipeline, no CMS. |
| Deployment | 2026-04-15 | |
| Features | 2026-04-15 | |
| Getting Started | 2026-04-15 | |
| Architecture | 2026-04-15 |
inkstone/docs
| File | date | summary |
|---|---|---|
| Callouts | 2026-04-16 | Obsidian callout boxes — all types, collapsible variants, and pinned-open behavior. |
| Code Blocks | 2026-04-16 | Fenced code blocks with syntax highlighting, language labels, and a copy-to-clipboard button. |
| Media Embeds | 2026-04-24 | Images, video, and audio embeds — lightbox, sliders, captions, inline illustrations, and width control. |
| Math and Diagrams | 2026-04-16 | LaTeX math via KaTeX and Mermaid diagrams — both rendered in the browser. |
| RSS and Sitemap | 2026-04-16 | Site-wide and per-section RSS feeds, and the auto-generated sitemap. |
| Themes | 2026-04-24 | Dark, light, and system mode toggle, CSS theme architecture, and how to select or create a theme. |
| SEO and Metadata | 2026-04-16 | OpenGraph, Twitter Card, JSON-LD structured data, banner images, and author metadata. |
| Obsidian Bases | 2026-04-30 | Publish .base database views as filtered, sorted HTML tables — filename markers, filter syntax, and available fields. |
| Note Templates & Authoring Workflow | 2026-04-23 | How to create new notes with correct InkStone frontmatter — using QuickAdd or Obsidian's core Templates plugin. |
| Publishing and Privacy | 2026-04-16 | How website:true publishes a note, what private notes are, and the private page placeholder. |
| Obsidian Syntax | 2026-04-16 | Checkboxes, highlights, footnotes, and block IDs — Obsidian inline syntax rendered server-side. |
| Attachments | 2026-04-16 | Media file resolution order — section _attachments/, vault root, and ATTACHMENTS_PATH fallback. |
| Comments | 2026-04-20 | Add a comment section to posts using Giscus — GitHub Discussions as a backend. |
| Note Transclusion | 2026-04-16 | Embed another note's content inline with ![[Note Title]] or transclude a specific heading. |
| Hot Reload | 2026-04-16 | The server detects vault file changes and reloads automatically — no restart needed. |
| Frontmatter Reference | 2026-04-16 | Complete reference for all InkStone frontmatter fields. |
| Pagination | 2026-04-16 | How listing pages paginate regular posts — page size, URL parameter, and featured post behavior. |
| Dataview | 2026-04-16 | Server-side Dataview queries — TABLE, LIST, FROM, WHERE, SORT, GROUP BY, LIMIT, and inline expressions. |
| Branding | 2026-04-17 | Favicon override, site icon beside the title, and per-section header title — all controlled from frontmatter. |
| Search | 2026-04-16 | Full-text search across all published posts, with optional tag filter. |
| Navigation | 2026-04-16 | Auto-generated nav, menu_order pinning, breadcrumbs, prev/next post links, and related posts. |
| Social Links | 2026-04-20 | Add social profile links to the footer — icon + handle, auto-detected from URL. |
| Documentation | 2026-04-16 | Complete InkStone reference — every feature documented in its own note. |
| Multilingual & UI Translations | 2026-04-23 | Publish content in multiple languages and translate all fixed UI text — no template editing required. |
| Canvas | 2026-04-24 | Publish Obsidian Canvas files as read-only visual boards — nodes, edges, arrows, file previews, and media embeds rendered from the .canvas JSON. |
| Wiki-Links | 2026-04-16 | How to link between notes using Obsidian wiki-link syntax — all forms supported. |
| Tags | 2026-04-16 | Frontmatter tags and inline #hashtags — archive pages, tag index, and Dataview filtering. |
| Post Types | 2026-04-16 | The four note types: homepage, listing, book, and regular posts — what each does and when to use it. |
Renders a heading for each group, then a sub-table of its rows.
Collapsed groups using rows.field
```dataview
TABLE rows.file.link as "Page", rows.date as "Date"
FROM #inkstone
GROUP BY file.folder
```
| File | "Page" | "Date" |
|---|---|---|
| InkStone Deployment Features Getting Started Architecture |
2026-04-15 2026-04-15 2026-04-15 2026-04-15 2026-04-15 |
|
| Callouts Code Blocks Media Embeds Math and Diagrams RSS and Sitemap Themes SEO and Metadata Obsidian Bases Note Templates & Authoring Workflow Publishing and Privacy Obsidian Syntax Attachments Comments Note Transclusion Hot Reload Frontmatter Reference Pagination Dataview Branding Search Navigation Social Links Documentation Multilingual & UI Translations Canvas Wiki-Links Tags Post Types |
2026-04-16 2026-04-16 2026-04-24 2026-04-16 2026-04-16 2026-04-24 2026-04-16 2026-04-30 2026-04-23 2026-04-16 2026-04-16 2026-04-16 2026-04-20 2026-04-16 2026-04-16 2026-04-16 2026-04-16 2026-04-16 2026-04-17 2026-04-16 2026-04-16 2026-04-20 2026-04-16 2026-04-23 2026-04-24 2026-04-16 2026-04-16 2026-04-16 |
When column expressions start with rows., InkStone renders one row per group, collecting values from all rows in that group.
Inline queries
Use `= expr` anywhere in note body to evaluate a field against the current note's frontmatter:
Published: `= date`
Author: `= author`
Tags: `= join(tags, ", ")`
this.field is an alias for the field name:
`= this.title`
Available fields
Every note exposes these in queries:
| Field | Value |
|---|---|
title |
Note title |
date |
Publication date |
updated |
Last-modified date |
summary |
Summary text |
tags |
List of tags |
author |
Author string or list |
section |
Vault section (e.g. blog) |
featured |
Boolean |
priority |
Number |
file.name |
Note title |
file.link |
HTML link to the note |
file.folder |
Folder path relative to vault root |
| Any frontmatter field | Accessible by its YAML key |
Expression functions
| Function | Usage | Result |
|---|---|---|
join(field, "sep") |
join(tags, ", ") |
Joins a list with separator |
join(list(a, b), "sep") |
join(list(title, date), " · ") |
Joins explicit values |
link(target, text) |
link(file.link, title) |
Constructs an anchor tag |
Private notes in queries
All vault notes — including those without website: true — are available to Dataview queries. This lets you use private notes as metadata sources or drafts while keeping them off the public site.
See also
- Tags — tag-based filtering in FROM/WHERE
- Publishing and Privacy — private notes as query sources
- Frontmatter Reference — all available frontmatter fields
InkStone can publish Obsidian .base files as filtered, sorted HTML tables. A Base is a YAML file that describes a database view over your vault notes — InkStone evaluates the filters and renders the result as a <table> inside a regular post page.
Publishing a base
The recommended way to publish a base is the filename marker: rename the file so it ends with __website before the .base extension.
All Posts__website.base
The page title is everything before __website — in this case "All Posts". The base is served at the same URL an .md file in the same folder would produce.
Obsidian Bases does not support arbitrary YAML keys — a website: true field in the .base YAML may be stripped when Obsidian re-saves the file. The filename marker survives re-saves.
Featuring on listing pages
Add __featured to the filename to pin the base in the Featured section of the parent listing:
All Posts__website__featured.base
The suffixes can appear in either order.
Legacy: website: true YAML field
Adding website: true to the .base YAML still works as a fallback:
website: true
title: All Posts
Use the filename marker for new bases — the YAML field is there for backwards compatibility.
Base YAML structure
A .base file is a YAML document. InkStone reads the following top-level fields:
| Field | Purpose |
|---|---|
title |
Page title (overrides filename-derived title) |
slug |
URL slug (auto-derived from title if omitted) |
date |
Publication date (YYYY-MM-DD) |
summary |
Shown on listing cards |
tags |
Content tags |
featured |
Alternative to __featured in filename |
author |
Shown in post meta |
banner |
Hero image URL |
banner_x / banner_y |
Focal point for the banner (percentage, 0–100) |
type |
View type — currently only table is rendered |
filters |
Array of filter conditions (see below) |
columns |
Column definitions — field (required), name (display label) |
sort |
Sort field |
sortOrder |
"asc" or "desc" |
limit |
Maximum number of rows |
Filters
Filters are evaluated against the dataview_index (one record per published .md note). Each filter is an object with a type field.
file.hasTag()
Matches notes that have the given tag.
filters:
- type: file.hasTag
tag: python
file.tags.contains()
Same as file.hasTag() — alternate syntax accepted by InkStone.
filters:
- type: file.tags.contains
value: "python"
file.inFolder()
Matches notes whose vault path starts with the given folder prefix.
filters:
- type: file.inFolder
folder: blog
Property comparison
Compares a frontmatter field against a value. Supported operators: =, !=, <, <=, >, >=.
filters:
- type: property
field: status
operator: "="
value: published
and / or / not
Combine or negate filters:
filters:
- type: and
filters:
- type: file.hasTag
tag: python
- type: file.inFolder
folder: blog
filters:
- type: not
filter:
type: property
field: draft
operator: "="
value: true
Example
A base that lists all published blog posts tagged python, sorted by date descending, showing title and date columns:
title: Python Posts
date: 2026-04-30
type: table
filters:
- type: and
filters:
- type: file.inFolder
folder: blog
- type: file.hasTag
tag: python
columns:
- field: title
name: Title
- field: date
name: Date
sort: date
sortOrder: desc
limit: 20
Save this as Python Posts__website.base in your vault to publish it at /python-posts.
See also
- Dataview —
TABLE/LISTqueries in fenced code blocks - Post Types — other special page types
- Publishing and Privacy —
website: trueand private notes
InkStone can publish Obsidian .canvas files as read-only visual boards. Nodes are rendered as positioned boxes, edges as SVG bezier curves with direction arrows, and edge labels as HTML overlays — all scaled to fit the page width.
Publishing a canvas
The recommended way to publish a canvas is the filename marker: rename the file so it ends with __website before the .canvas extension.
My Diagram__website.canvas
The page title is everything before __website — in this case "My Diagram". The canvas is served at the same URL that an .md file in the same folder would produce.
Obsidian strips unrecognised top-level JSON keys when it re-saves a canvas. That means "website": true disappears the next time you edit the file in Obsidian. The filename marker survives re-saves because Obsidian never touches the filename.
Top-level JSON fields
These optional JSON fields are read from the canvas file:
| Field | Purpose |
|---|---|
"title": "..." |
Overrides the filename-derived title |
"date": "YYYY-MM-DD" |
Publication date |
"summary": "..." |
Shown on listing cards |
"tags": [...] |
Content tags |
"featured": true |
Show in the Featured section of the parent listing |
"banner": "..." |
Hero image URL |
Legacy: "website": true JSON flag
Adding "website": true to the canvas JSON still works as a fallback, but it is fragile — Obsidian will remove it on the next re-save. Use the filename marker instead.
Node types
All four Obsidian node types are supported:
| Type | Rendered as |
|---|---|
text |
Box with rendered inline markdown (bold, italic, inline code, line breaks) |
file |
Card with a scrollable preview of the linked note, or inline media — see below |
link |
Box with a clickable external URL (opens in a new tab) |
group |
Dashed-border container rendered beneath other nodes; label appears on the top edge |
Text nodes
Text content supports a subset of inline markdown:
**bold**and__bold__*italic*`inline code`- Line breaks (newlines become
<br>)
File nodes
File nodes behave differently depending on what they point at:
Published post — if the file resolves to a published InkStone post, the node renders as a card with: - A clickable header showing the post title (links to the post URL) - A scrollable preview of the full rendered note body
Vault image, video, or audio — if the file points at a media file inside the vault (.jpg, .png, .gif, .webp, .svg, .mp4, .webm, .mov, .mp3, .ogg, .wav, .flac, .m4a), the media is embedded inline inside the card.
Unresolved file — if the file is not a published post or recognised media, the node shows the filename as plain text.
Node colours
Obsidian's six preset colours are supported:
| Value | Colour |
|---|---|
"1" |
Red |
"2" |
Orange |
"3" |
Yellow |
"4" |
Green |
"5" |
Cyan |
"6" |
Purple |
The colour is applied as the node's border colour. Unset nodes use the default theme border.
Edges
Edges are drawn as SVG bezier curves with a direction arrow at the target end. The arrowhead colour matches the edge stroke.
The control-point length scales with the distance between connected sides so curves stay smooth at any layout.
Edge labels
If an edge has a "label" field, the label renders as a small HTML badge at the visual midpoint of the curve — not as SVG text, which would be distorted by the aspect-ratio scaling. The badge has a background so it stays readable over node boxes.
Edge colours
Edge colour follows the same six-value scheme as node colours. Edges without a "color" use the theme's muted text colour.
Sizing and layout
The canvas is rendered at the full width of the article column. The height is set automatically to preserve the original aspect ratio of all nodes combined (plus padding). Nodes are positioned with percentage coordinates so the layout stays correct at any viewport width.
Canvas nodes have fixed sizes defined by you in Obsidian. If a text node is too small for its content, the overflow is hidden (with a subtle fade). Resize nodes in Obsidian until the content fits — what you see in Obsidian is what gets published.
Example
A minimal two-node canvas (save as Simple Diagram__website.canvas):
{
"title": "Simple Diagram",
"nodes": [
{
"id": "a",
"type": "text",
"text": "**Start**",
"x": -200, "y": -60,
"width": 160, "height": 80,
"color": "5"
},
{
"id": "b",
"type": "text",
"text": "**End**",
"x": 80, "y": -60,
"width": 160, "height": 80,
"color": "4"
}
],
"edges": [
{
"id": "e1",
"fromNode": "a", "fromSide": "right",
"toNode": "b", "toSide": "left",
"label": "next"
}
]
}
See also
- Media Embeds — inline images and galleries in regular notes
- Post Types — other special page types (
homepage,listing,book) - Bases — publish
.basedatabase views as filtered HTML tables - Dataview — server-side query tables in fenced code blocks
SEO & Feeds
InkStone generates rich metadata for every page automatically — no plugins or third-party services required.
OpenGraph / Twitter Card
Every page gets <meta> tags for social sharing:
| Meta tag | Source |
|---|---|
og:title / twitter:title |
Post title |
og:description / twitter:description |
Post summary |
og:image / twitter:image |
banner frontmatter URL |
og:url |
Canonical URL of the page |
og:type |
article for posts, website for homepage |
twitter:card |
summary_large_image if banner set, else summary |
These tags are injected in a {% block meta %} in base.html and overridden per page.
Banner image
Add a banner image to any note:
banner: "https://example.com/photo.jpg"
banner_x: 0.5 # horizontal focal point, 0–1 (default: 0.5)
banner_y: 0.4 # vertical focal point, 0–1 (default: 0.5)
The banner is used as the OpenGraph / Twitter Card image. It also appears as a hero image at the top of the post page, with CSS object-position set from banner_x and banner_y so the focal point stays visible at any aspect ratio.
JSON-LD structured data
InkStone injects a <script type="application/ld+json"> block on each page for Google rich results.
Article schema (regular posts)
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Post Title",
"datePublished": "2026-04-16",
"dateModified": "2026-04-20",
"author": [{"@type": "Person", "name": "Jane Doe"}],
"image": "https://example.com/banner.jpg"
}
Book schema (type: book posts)
{
"@context": "https://schema.org",
"@type": "Book",
"name": "Book Title",
"author": [{"@type": "Person", "name": "Jane Doe"}]
}
WebSite schema (homepage)
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Site Name",
"url": "https://example.com"
}
Author field
author: "Jane Doe"
Or multiple authors:
author:
- Jane Doe
- John Smith
Shown in post meta (below the title) and included in JSON-LD author array.
Updated date
updated: 2026-04-20
Shown as "Updated April 20, 2026" in post meta. Maps to dateModified in JSON-LD.
Reading time
Estimated reading time is computed at load time from word count (200 words per minute) and shown on post pages and listing cards.
Canonical URL
Each page's canonical URL is injected as <link rel="canonical"> using request.base_url (the URL without query parameters). This prevents duplicate-content issues with paginated listing pages.
Sitemap
All published URLs are listed in /sitemap.xml. See RSS and Sitemap for details.
See also
- RSS and Sitemap —
feed.xmlandsitemap.xml - Frontmatter Reference —
banner,banner_x,banner_y,author,updated - Post Types — how
type: bookuses Book JSON-LD
InkStone auto-generates RSS feeds and a sitemap for every deployment — no configuration needed.
Site-wide RSS feed
URL: /feed.xml
- Contains the 20 most recent published posts across all sections, sorted by date (newest first).
- Each item includes: title, link, GUID, pubDate, and description (the post summary).
- The feed title and link use
WEBSITE_NAME(from the root homepage title).
Per-section RSS feed
URL: /<section>/feed.xml
Example: /blog/feed.xml, /gallery/feed.xml
- Contains the 20 most recent posts in that section (including sub-sections), sorted by date.
- The section must have a registered route (a
type: listingortype: homepagenote, or an auto-generated listing). - Returns 404 if the section doesn't exist.
Feed format
Both feeds are valid RSS 2.0 XML:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>My Site</title>
<link>https://example.com</link>
<description>My Site</description>
<lastBuildDate>...</lastBuildDate>
<item>
<title>Post Title</title>
<link>https://example.com/blog/post-slug</link>
<guid>https://example.com/blog/post-slug</guid>
<pubDate>Wed, 16 Apr 2026 00:00:00 +0000</pubDate>
<description>Post summary text...</description>
</item>
...
</channel>
</rss>
Sitemap
URL: /sitemap.xml
Auto-generated sitemap that lists every public URL on the site:
- The root
/ - All section root URLs (from
SECTION_ROUTES) - All regular post URLs (from
ALL_POSTS)
Format: XML Sitemap Protocol 0.9.
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc></url>
<url><loc>https://example.com/blog</loc></url>
<url><loc>https://example.com/blog/my-post</loc></url>
...
</urlset>
Private notes, tag archive pages, and search/tags index pages are not included.
Linking to feeds
Add feed autodiscovery to your site by referencing the feed URL in templates, or simply share the URL with subscribers. Most RSS readers also auto-detect /feed.xml.
See also
- SEO and Metadata — OpenGraph, JSON-LD, canonical URLs
- Publishing and Privacy — which posts appear in feeds
Appearance & Dev
InkStone ships with its own logo and favicon. All three branding elements — the favicon, the icon beside the site title, and the displayed title itself — can be overridden from your vault with no code changes.
Favicon
InkStone serves its built-in logo at /favicon.ico, /favicon.png, and /favicon.svg by default.
To use your own favicon, place a file named favicon.ico, favicon.png, or favicon.svg in the root of your vault. InkStone checks for it on every request and serves it in place of the default. The first match wins in this order: .ico → .png → .svg.
your-vault/
favicon.ico ← drop it here to override
homepage.md
blog/
...
No config, no restart needed — the hot-reload file watcher picks it up automatically.
Site icon (beside the title)
Add an icon: field to any note's frontmatter to show an image beside the site title in the header.
---
website: true
type: homepage
title: My Site
icon: _attachments/my-logo.png
---
Path formats
| Value | How it's served |
|---|---|
_attachments/logo.png |
Vault-relative — served via /attachments/_attachments/logo.png |
/static/logo.svg |
Engine static asset — served directly |
https://example.com/logo.svg |
External URL — used as-is |
Cascade inheritance
The icon cascades down the URL hierarchy. Set it once on a section homepage and every page in that section inherits it automatically.
Example: setting icon on the /inkstone homepage means all of /inkstone/getting-started, /inkstone/docs/dataview, etc. show the same icon — unless one of those pages sets its own icon: to override it.
The resolution order for any page at /a/b/c is:
/a/b/c(the page itself)/a/b/a/(root homepage)
The first ancestor that has icon: set wins. If none do, no icon is shown.
Custom header title
site_title: replaces the website name displayed in the header <h1> for a page and all its children.
---
website: true
type: homepage
title: InkStone Docs
site_title: "InkStone"
---
This is useful when a section of your site has a distinct brand from the root. The cascade rules are identical to icon: — set it on a section homepage and every child page inherits it.
icon: and site_title: are independent — you can set either or both. A child page can override one without affecting the other.
Full example
Root homepage — sets the default icon and title for the whole site:
---
website: true
type: homepage
title: Anton Bakulin
icon: _attachments/avatar.jpg
---
InkStone section homepage — overrides both for everything under /inkstone:
---
website: true
type: homepage
title: InkStone
icon: /static/logo.svg
site_title: "InkStone"
---
A single post — overrides the icon for that page only:
---
website: true
title: My Photography
icon: _attachments/camera-icon.svg
---
See also
- Frontmatter Reference — complete field list
- Themes — dark/light mode toggle and CSS theme files
- Navigation —
menu_orderand breadcrumbs
InkStone ships with two CSS themes and a dark/light mode toggle. All styling is in plain CSS — no build step.
Theme toggle
A three-state toggle in the header cycles through System → Light → Dark → System:
| Icon | Mode | Behaviour |
|---|---|---|
| ⊙ | System | Follows prefers-color-scheme; updates live if the OS theme changes |
| ☀ | Light | Always light, regardless of OS setting |
| ☾ | Dark | Always dark, regardless of OS setting |
The preference is stored in localStorage. When no value is saved (or after selecting System), the site follows the OS prefers-color-scheme media query in real time.
The toggle works by setting a data-theme="light" or data-theme="dark" attribute on the <html> element. CSS variables handle colour differences so a single attribute covers the entire site.
CSS file structure
| File | Description |
|---|---|
frontend/static/base.css |
All layout, typography, components. Uses CSS custom properties throughout. Obsidian palette is the default. |
frontend/static/callouts-base.css |
Callout box structure and icons — theme-agnostic. |
frontend/static/theme-obsidian.css |
Callout type colours for the Obsidian theme (dark + light). |
frontend/static/theme-omarchy.css |
Catppuccin Mocha/Latte variable overrides + callout type colours. |
frontend/static/code.css |
Code block styles: Tokyo Night Dark syntax highlighting, language labels, copy button. |
base.css + callouts-base.css are always loaded. Only one theme-*.css file is loaded at a time, selected from the frontmatter.
Default theme for new visitors
Set default_theme in your root homepage's frontmatter to control what new visitors see before they've touched the toggle:
---
website: true
type: homepage
title: My Site
default_theme: dark # "dark" | "light" | "system" (default)
---
| Value | Behaviour |
|---|---|
dark |
Forces dark mode for new visitors |
light |
Forces light mode for new visitors |
system |
Follows the visitor's OS prefers-color-scheme (default if omitted) |
Visitors can always override with the toggle; their choice is saved in localStorage and takes precedence on return visits.
Selecting a theme
Site-wide theme
Add theme to your root homepage's frontmatter:
---
website: true
type: homepage
title: My Site
theme: omarchy
---
This applies the selected theme to every page on the site. If theme is not set, the site defaults to obsidian.
Per-page theme override
Any individual page can override the site theme with its own theme frontmatter field:
---
website: true
title: My Special Page
theme: omarchy
---
That page renders with the overridden theme; all other pages continue using the site-wide default.
Priority: page frontmatter → homepage frontmatter → obsidian (built-in fallback).
Creating a new theme
The CSS token architecture means a new theme only needs to declare the variables it differs from the defaults. A theme that changes nothing but the background:
/* theme-custom.css */
:root {
--bg: #2d1b33;
--bg-raised: #251528;
--banner-gradient: transparent, rgba(45,27,51,0.5), #2d1b33;
}
Place the file in frontend/static/ as theme-custom.css, then reference it in your homepage frontmatter:
theme: custom
Everything else (layout, typography, callout structure, navigation) inherits from base.css. Only the variables you declare are overridden.
CSS custom property reference
Key variables defined in base.css that themes can override:
| Variable | Purpose |
|---|---|
--bg |
Page background |
--bg-raised |
Elevated surfaces (code blocks, mermaid, transclusions) |
--bg-card |
Card / form surfaces |
--bg-stripe |
Table alternating row tint |
--bg-hover |
Row / badge hover tint |
--text |
Body text |
--text-muted |
Secondary text |
--text-dim |
Tertiary text (breadcrumbs, pagination info) |
--accent |
Links, active borders, highlights |
--site-title |
Site title colour in header |
--border |
Primary border colour |
--border-subtle |
Soft inner borders (post entries, search results) |
--h1-color … --h6-color |
Per-heading colour tokens |
--callout-base-bg |
Default callout background |
--callout-base-border |
Default callout border-left colour |
--tag-color |
Tag badge text colour |
--banner-meta-color |
Subtitle / meta text on banner images |
--inline-code-bg / --inline-code-text |
Inline code chip |
--mark-bg / --mark-fg |
==highlight== background and text |
Print stylesheet
A @media print stylesheet is included in base.html. When printing, it:
- Hides the nav, header controls, and sidebar
- Resets colours to black on white
- Appends the full URL after every link (
a[href]::after { content: " (" attr(href) ")"; })
Mobile styles
Responsive layout adjusts at 600 px viewport width:
- Nav links wrap below the site title (no hamburger — always visible)
- Breadcrumbs stay on a single horizontal line
- Listing cards stack vertically
- Font sizes scale down slightly
See also
- Code Blocks — code block syntax highlighting (Tokyo Night Dark)
- Math and Diagrams — Mermaid diagram theme adaptation
InkStone renders fenced code blocks with syntax highlighting, a language label badge, and a copy-to-clipboard button — all without client-side plugins.
Basic usage
```python
def hello(name: str) -> str:
return f"Hello, {name}!"
```
Renders as:
def hello(name: str) -> str:
return f"Hello, {name}!"
Features:
- Syntax highlighting — Tokyo Night Dark colour scheme via Python-Markdown's codehilite extension
- Language label — shown in the top-right corner of the block
- Copy button — appears on hover (or always on mobile); copies the code content to the clipboard
Supported languages
Any language supported by Pygments works. Common examples:
| Language | Fence tag |
|---|---|
| Python | ```python |
| JavaScript | ```javascript or ```js |
| TypeScript | ```typescript or ```ts |
| Bash / Shell | ```bash or ```sh |
| SQL | ```sql |
| YAML | ```yaml |
| JSON | ```json |
| HTML | ```html |
| CSS | ```css |
| Rust | ```rust |
| Go | ```go |
| Markdown | ```markdown |
Unsupported or omitted language tags render as plain text in a <code> block, without a label.
Inline code
Backtick spans render as <code> elements:
Use the `print()` function.
Inline code is not syntax-highlighted — it's styled with the code font and a subtle background.
Styling
Code block styles live in frontend/static/code.css. To change the colour scheme, replace the codehilite-generated CSS classes or swap out the code.css file entirely.
The language label is a <span class="code-lang"> element positioned in the top-right corner via CSS.
Mermaid and Dataview blocks
Fenced blocks with mermaid or dataview language tags are intercepted before reaching the standard code renderer:
mermaidblocks → rendered as SVG diagrams (see Math and Diagrams)dataviewblocks → executed as server-side queries (see Dataview)
See also
- Math and Diagrams — Mermaid diagrams and KaTeX math
- Dataview —
```dataview ```blocks - Themes — switching the overall site theme
InkStone watches your vault for file changes and reloads automatically on the next request. There is no file watcher process — the check happens in the request path.
How it works
On every incoming request, maybe_reload() runs:
- Checks the newest file modification time across all vault files.
- Compares it against the last known scan time.
- If anything changed, re-runs the full
load_posts()pipeline and swaps in the new data.
A 2-second debounce prevents multiple simultaneous reloads — if a request arrives within 2 seconds of the last check, the check is skipped.
What triggers a reload
Any change to any file in the vault directory:
- Editing a
.mdnote - Creating or deleting a note
- Adding or removing media files in
_attachments/ - Modifying frontmatter
Thread safety
Reloads are protected by a threading.Lock. If two requests arrive simultaneously and both detect a change, only one performs the reload — the other skips it and serves the previous (stale-by-milliseconds) data.
Development workflow
- Start the server:
python3 app.py - Edit notes in Obsidian (or any editor)
- Save and refresh the browser — the change is live
No --watch flags. No separate process. No restart.
Production note
Hot reload also works in production. When you push a vault update and Coolify rebuilds the Docker image, the new vault is loaded at startup. If you're running a long-lived process (gunicorn) with the vault mounted as a volume, changes are picked up automatically on the next request.
See also
- Getting Started — running the development server
- Deployment — production deployment with Coolify
InkStone supports opt-in comments via Giscus — a lightweight comment system backed by GitHub Discussions. When enabled, a comment section appears at the bottom of every post and book page.
How it works
Giscus stores comments as GitHub Discussions in a repository you choose. Visitors log in with their GitHub account to comment. No database, no ads, no tracking beyond what GitHub itself does.
Setup
1. Prepare your GitHub repo
The repo that holds discussions must be:
- Public
- Have the Discussions feature enabled (Settings → Features → Discussions ✓)
You can use your site's source repo or a dedicated repo for discussions.
2. Get your IDs from giscus.app
Go to giscus.app, enter your repo, choose a mapping (Pathname is recommended), and select a discussion category. The page will give you:
- Repo — e.g.
you/your-site - Repo ID — a base64 string starting with
R_ - Category ID — a base64 string starting with
DIC_
3. Set environment variables
Add the three values to your .env (local) or deployment environment:
GISCUS_REPO=you/your-site
GISCUS_REPO_ID=R_kgDO...
GISCUS_CATEGORY_ID=DIC_kwDO...
When all three are set, InkStone automatically injects the Giscus embed at the bottom of post.html and book.html. If any variable is missing, comments are not shown.
Theme sync
Giscus automatically follows InkStone's dark/light mode toggle. When you switch themes, the comment section updates instantly — no page reload required.
Disabling comments per-post
There is no per-post opt-out at the moment. Comments are shown on all posts and books when the env vars are set. If you need to suppress comments on specific pages, leave the env vars unset (site-wide off) or open a feature request.
See also
- Deployment — full environment variable reference
- Post Types — which templates show the comment section (
post.html,book.html)
InkStone can display your social profiles in the footer as icon + handle pairs. The network is detected automatically from the frontmatter key name; the handle is extracted from the URL.
Setup
Add one key per platform to your root homepage frontmatter:
---
website: true
type: homepage
title: My Site
github: https://github.com/yourname
mastodon: https://mastodon.social/@yourname
bluesky: https://bsky.app/profile/yourname.bsky.social
---
That's all. InkStone shows the platform icon, extracts @yourname from the URL, and renders it in the footer. Hovering a link shows a tooltip with the full network name and handle.
Supported platforms
| Key | Network | Handle from URL | rel="me" |
|---|---|---|---|
github |
GitHub | @username |
✓ |
mastodon |
Mastodon | @username |
✓ |
bluesky |
Bluesky | @username (.bsky.social stripped) |
✓ |
twitter |
X / Twitter | @username |
|
instagram |
@username |
||
linkedin |
username |
||
facebook |
username |
||
youtube |
YouTube | @handle |
rel="me" is set on GitHub, Mastodon, and Bluesky links — the platforms used for identity verification (e.g. Mastodon profile verification).
How handles are extracted
InkStone takes the last path segment of the URL and prepends @ where appropriate:
| URL | Displayed as |
|---|---|
https://github.com/airenare |
@airenare |
https://mastodon.social/@airenare |
@airenare |
https://bsky.app/profile/airenare.bsky.social |
@airenare |
https://x.com/airenare |
@airenare |
https://linkedin.com/in/airenare |
airenare |
Ordering
Links appear in the footer in a fixed order: GitHub → Mastodon → Bluesky → X → Instagram → LinkedIn → Facebook → YouTube. The order matches the registry — any platform you haven't set simply doesn't appear.
See also
- Frontmatter Reference — full list of root homepage fields
- Comments — Giscus comment system
InkStone