Adjust site identity, visual tokens, navigation, and repository links.
Customization
The bundled example defaults live in website/_config.yml. Host repositories should put overrides in .github/jekyll-obsidian.yml, generated by website/bin/integrate, so website/ remains replaceable. The effective public configuration stays small enough to review in one place:
title: My Site
description: Built from Markdown
lang: en
url: ""
baseurl: ""
website:
# jekyll-obsidian:managed-start
source: docs
theme: minimal
# jekyll-obsidian:managed-end
syntax_profile: ofm@1
repository: owner/repository
edit_branch: main
content:
publish_by_default:
default_type: doc
directories:
post:
doc:
features:
contacts:
- label: GitHub
url: https://github.com/owner
- label: Email
url: mailto:hello@example.com
website.source is relative to the host repository root. The bundled defaults use source: website/docs; a generated host override normally uses source: docs or another directory outside website/.
The canonical commands merge website/_config.yml, then .github/jekyll-obsidian.yml, then temporary command-line or Pages values. The integration command owns only the marked source and theme lines in the host file; titles, repository links, content classification, and feature overrides remain editable around that block. bin/build pins the Jekyll source, implementation directories, caches, destination, and safety settings to website/. Direct jekyll commands do not load the host overlay or these ownership safeguards and are not a supported entrypoint.
website.theme selects minimal or docs. website.syntax_profile pins the Markdown contract and currently accepts only ofm@1. website.edit_branch selects the branch used by Edit and other host source links; it defaults to main.
Publication defaults
website.content.publish_by_default is an array of directories relative to website.source. Each entry selects Markdown files in that directory and its descendants. The default is an empty array, which keeps publish: true as the required opt-in. Use . when every Markdown file in the content tree should publish unless it opts out:
website:
content:
publish_by_default:
- .
An explicit YAML boolean always wins. publish: false excludes one note from a selected directory, while publish: true includes one note outside those directories. Publication directories do not classify content; default_type, directories.post, directories.doc, and a note's content_type still decide whether a published note is a page, post, or document. Attachments remain private unless a published note, its image property, or its transclusion closure references them.
Site identity
title, description, and lang feed the shell, metadata, Atom, and accessibility labels. Set website.repository to an owner/repository pair to show the Edit link. If it is blank, the build checks GITHUB_REPOSITORY and the local origin remote. The action stays hidden when no repository can be identified. This host repository setting never changes the Built by Jekyll Obsidian footer link, which always identifies the official project at https://github.com/wowfun/jekyll-obsidian.
Minimal Home renders the public root directory index, followed by the six most recent posts as editorial rows. The index is index.md, or the exact sibling README.md when no physical index.md exists. A selected directory index without an authored level-one heading does not render a substitute heading from its filename or frontmatter title; the resolved title remains available to metadata and navigation. A row with an image places a 16:9 thumbnail beside its summary on wide screens and above it on narrow screens; a row without an image uses the full width with no placeholder. Each summary can also show subtitle, a body excerpt, authors, and its publication date. The Blog link opens the complete reverse-chronological list at /blog/.
Add website.contacts to place contact links after the recent-post section. Each entry requires a short label and an https:, mailto:, or tel: URL; use an empty list or omit it when the base configuration does not define contacts. Email, phone, GitHub, LinkedIn, X/Twitter, Mastodon, Bluesky, Instagram, YouTube, Telegram, RSS, and Website links receive an accessible icon automatically. Other providers keep their label as text, so the configuration stays provider-neutral and needs no icon field.
Site themes
Each build selects one complete presentation preset. minimal supports a Home page, Blog, Docs, and custom sections in one general-purpose shell. docs provides a focused hierarchical handbook. Search, wiki-link previews, page outlines, note relations, and the interactive graph are shared capabilities in both themes. A command-line override is useful for comparing the same content without editing configuration:
The build writes the named destination below website/, so _site becomes website/_site. The supported theme identifiers are minimal and docs.
Feature keys omitted from website.features inherit the theme defaults. Explicit YAML booleans can override search, tags, feed, graph, relations, previews, and outline.
| Feature | Minimal | Docs |
|---|---|---|
search, previews, outline, relations, graph |
on | on |
tags, feed |
on | off |
Set any shared feature to the YAML boolean false to remove it. For example:
website:
features:
graph: false
previews: false
Minimal navigation
Minimal begins with Home, Blog, Docs, and Portfolio, ordered at 0, 10, 20, and 30. Blog and Docs disappear automatically when the content tree has no public post or documentation page. Portfolio uses the source-relative portfolio folder by default and appears when that folder contains at least one visible published project. An empty or unpublished folder adds no tab. Override built-in labels, order, visibility, or the Portfolio path under website.navigation:
website:
theme: minimal
navigation:
home:
label: Home
order: 0
visible: true
blog:
label: Writing
order: 10
visible: true
docs:
label: Handbook
order: 20
visible: true
portfolio:
path: work
label: Work
order: 30
visible: true
The Portfolio path is relative to website.source; an explicit path replaces the default portfolio path. Published Markdown descendants become Portfolio pages, so an explicit content_type within that path must be page. A public directory index keeps its authored introduction above the project grid. Use <path>/index.md, or <path>/README.md when no physical index exists. When the selected index is not public, the compiler generates the Portfolio index at that route. The selected index itself is not a project. Projects with pinned: true appear first; each group is then ordered by nav_order, title, and path. Set nav_exclude: true on a project to omit its card, or set website.navigation.portfolio.visible: false to hide only the tab while keeping the Portfolio index and project pages public.
Each project card uses its image, title, and description, falling back to a preview from the body when no description is present. Local GIF, WebP, AVIF, and APNG files are copied byte for byte and rendered with <img>, which preserves animation. The compiler does not transcode them or generate thumbnails.
A project can replace its local body with a public GitHub Markdown file by setting github_markdown. The local wrapper continues to own card metadata and its route, while the resolved remote body supplies the detail page, outline, Search text, preview, and Markdown endpoint. See Portfolio for the accepted URL and mapping forms, empty-body rule, branch refresh behavior, content limits, relative URLs, localization, and security boundary.
Custom tabs
Minimal and Docs use the same custom-tab contract. Declare a tab on a published folder index, either index.md or its eligible README.md fallback. That note remains the canonical tab homepage, and its authored body appears above the generated member cards:
---
publish: true
content_type: page
tab:
id: favorites
label: Favorites
order: 40
topics:
- favorite
- reference
---
id is required and must be a lowercase ASCII identifier such as favorites or team-work; home, blog, docs, and portfolio are reserved. The label defaults to the localized index title, and order defaults to 100. A custom tab is always visible. Its root must be a visible content_type: page folder index and cannot sit inside the active Portfolio path.
Members are the union of three sources: every published descendant of the tab folder, any published note that names the tab in tabs, and any published note whose tags or categories match one of the tab's topics. Multiple topics use OR matching after Unicode normalization and case folding. Add a page outside the folder without changing its route:
---
publish: true
tabs:
- favorites
---
Membership is additional: a Blog post remains active under Blog, a documentation page remains active under Docs, and a Portfolio page remains active under Portfolio. Only ordinary page descendants use the custom tab as their active navigation item; the deepest nested custom root wins. Cards are de-duplicated and sorted by pinned, then nav_order, localized title, and source path. nav_exclude: true removes a folder or topic match and cannot be combined with tabs. An empty topic result is valid and leaves only the authored index body.
Duplicate IDs, labels, or destinations, unknown tabs references, invalid roots, and reserved conflicts fail the build. Notes keep one canonical URL even when several tabs include them; custom tabs never create redirects or duplicate Search, feed, or sitemap entries.
Tabs that do not fit the desktop header move into an accessible More menu. The mobile Browse sheet and the Search dialog's quick navigation use the same ordered destinations and active state. Search narrows those quick links by their configured labels while it queries note content. Without JavaScript, the header links remain visible and wrap naturally. Opening Docs from Minimal keeps the Minimal site shell while adding the handbook tree, page outline, and previous or next links.
Graph and wiki-link previews
A published note places its one-hop graph above Outline and Relations in the right-hand context rail only when it links to, embeds, is linked from, or is embedded by another public note in the current language partition. The graph contains the current note and those direct neighbours. Isolated notes and notes with only self-links omit the local graph, while the complete graph still contains their nodes. Node area grows with its degree in the complete public graph.
Use the graph's left button to open the complete graph and its right button to enlarge the current note's local graph. In either view, scroll the mouse wheel over the canvas to zoom around the pointer, drag empty canvas space to pan, and drag a neighbouring node to reposition it; the current node stays fixed at the visual centre. Click a node to visit that note, or focus it and press Enter or Space. The complete graph JSON loads only when its dialog is opened and always contains every public node and relation. To keep the page responsive, the SVG viewer declines to render complete graphs above 250 nodes or 1,000 relations and directs readers to local graphs or search instead. There is no generated /graph/ page or navigation tab, so a published note may use that route.
Rest the pointer on a wiki link for 0.3 seconds, or keyboard-focus it, to open its reading preview. Leaving before the pointer delay cancels the preview; keyboard focus remains immediate. Catalog metadata appears first, followed by a compact, independently scrolling view of the target note's sanitized body. The preview title opens the target note; links inside the body remain plain text. Touch taps on the original wiki link continue directly to the target page. Set previews: false to disable this behavior.
GitHub Discussions comments
Both themes can attach a GitHub Discussion to each content_type: post through Giscus. Comments are disabled when website.comments is absent. When the mapping is present and omits enabled, Minimal enables comments while Docs keeps them disabled. To use comments, first prepare a public GitHub repository:
- Enable Settings → General → Features → Discussions.
- Create a
Blog commentscategory. The Announcement format is recommended because maintainers and Giscus can create discussions there while visitors can still reply. - Install the Giscus GitHub App for that repository.
- Enter the repository and category at giscus.app and copy their generated IDs.
Then add the values to the host configuration:
website:
theme: minimal
repository: owner/site
comments:
# Optional. Omit this to reuse website.repository.
repository: owner/community
repository_id: R_kgDOxxxxxxxx
category: Blog comments
category_id: DIC_kwDOxxxxxxxx
Add enabled: true inside comments when the active theme is Docs; add enabled: false to keep a configured Minimal site dormant. The comments repository may be the publication repository or a separate public community repository. The build never checks whether Discussions is enabled or the Giscus App is installed. Missing provider IDs produce a warning and a non-interactive fallback instead of failing the build. Add a giscus.json file to the comments repository when you want to restrict embedding to the production site origin.
Once enabled, every content_type: post has comments in any theme. Disable one article with a YAML boolean:
---
publish: true
content_type: post
comments: false
---
Threads use a strict, route-independent term derived from the note path. Changing a domain, baseurl, or permalink keeps the thread; moving or renaming the source note creates a new identity. Reactions are enabled, the input appears above existing comments, and Giscus lazily loads its iframe near the comments area. The Giscus client itself is still requested when a published comment page initializes.
Local development does not connect to Giscus. It shows a publication-only notice and a normal link to the repository's Discussions page. Published and CI builds load the widget, synchronize it with the site's light or dark scheme, and retain the GitHub link when JavaScript or the external service is unavailable. Localized pages share the post's route-independent Discussion thread, while the Giscus interface follows each page locale when supported and otherwise falls back to English.
See Comments with GitHub Discussions for thread identity, origin restrictions, privacy boundaries, and troubleshooting.
Analytics
Analytics is off when website.analytics is absent. A production site can select Cloudflare Web Analytics:
website:
analytics:
provider: cloudflare
token: SITE_TOKEN
Or it can select one Google Analytics 4 property:
website:
analytics:
provider: google
measurement_id: G-XXXXXXXXXX
Only one provider may be active. Local development and redirect pages do not load either client. website/bin/integrate preserves this mapping but does not create it. See Analytics for provider setup, Docs navigation tracking, Content Security Policy sources, privacy differences, and troubleshooting.
Localization
Both themes can opt into static localization by listing locales under website.i18n. The mapping enables localization by default for Docs and stays disabled by default for Minimal; set enabled: true there. The top-level lang is the default locale and must appear in the list when localization is enabled:
lang: en
website:
theme: docs
i18n:
locales:
- en
- zh-CN
Add enabled: true inside i18n for Minimal; add enabled: false to keep a configured Docs locale plan dormant. Keep the default language in the normal content tree. Put translated notes at the same relative path below _translations/<locale>/. A translation inherits the publication state of its public default-language note; set publish: false on the translation to show the existing default-language fallback instead. Every configured locale requires _locale.yml at its locale root; name is required, while hreflang, dir, and the closed messages catalog are optional. A missing or disabled translation does not fail the build. It keeps its localized URL, briefly shows a notice over the default-language content, and is excluded from search-engine indexing and the sitemap.
name: 简体中文
hreflang: zh-Hans
dir: ltr
messages:
search: 搜索
dir accepts only ltr or rtl. Message values must be strings and keys must belong to the closed built-in theme catalog; unknown keys fail the build. Omitted messages use the built-in English text. The locale list order is also the language-switcher order.
The default locale keeps existing URLs. Other locales use their configured tag as a prefix, such as /zh-CN/docs/Getting%20Started/. Navigation, search, theme system pages, language switching, SEO metadata, and locale assets stay partitioned for Minimal and Docs alike.
See Localization guide for locale manifests, translation authority, fallback pages, SEO behavior, and troubleshooting.
Color and type
The themes share a narrow set of CSS custom properties. Light mode uses warm paper and surface tones; dark mode uses neutral black and gray. Blue marks links and focus, while terracotta marks relationships and secondary annotations.
Articles and controls use the platform sans-serif stack, with installed Noto Sans CJK or Source Han Sans preferred for CJK text. Code uses the platform monospace stack. The site does not download a webfont, so the exact face follows the reader's operating system while the typographic scale and spacing remain consistent.
Override tokens in your own stylesheet rather than editing hashed build output. Keep text and focus contrast above WCAG AA in both color schemes.
Page properties
The compiler reserves these note properties for publication behavior and validates each one with a closed schema:
publish,title,subtitle,aliases,tags,author,categories, anddescriptionpermalink,image, andcssclassescreatedandupdatedcontent_type,date,pinned,nav_order,nav_exclude,tab, andtabscomments,github_markdown, andrelated
Other top-level properties may contain scalar values or flat scalar lists. Custom property names must be normalized, single-line text without leading or trailing whitespace. A complete wiki link in one of those values becomes an ordinary note relation when it is a YAML double-quoted string. Custom properties remain compiler metadata: their keys and raw values never flow into Liquid, HTML, feeds, or generated JSON.
project: "[[portfolio/fauni-search|Fauni Search]]"
references:
- background
- "[[docs/development/architecture]]"
related:
- "[[blog/launch-notes]]"
- "[[docs/Getting Started|Start here]]"
related is the curated list variant. It accepts only a list of double-quoted wiki links, preserves the authored order, removes repeated targets, and shows the resolved pages as Recent-post-style cards at the bottom of the page. Broken related links fail a production build. Other custom-property links still join Direct links, Backlinks, and Graph data but do not create a second visible field table.
aliases, tags, author, categories, cssclasses, and tabs are string arrays; subtitle is a string. publish, pinned, nav_exclude, and comments use YAML booleans. tab is the closed mapping documented above. github_markdown accepts only the URL or mapping documented in Portfolio, and only on a Portfolio project wrapper. Dates use ISO 8601. A note title comes from title, its first level-one heading, or its filename, in that order.
updated is optional and appears in page metadata only when the author supplies it; the compiler never infers an update date from Git. A post's publication time uses date, then created, then its first Git commit. Atom entries use explicit updated when present and otherwise use that publication time for posts. A non-post note without updated is omitted from the feed.
Minimal Home shows subtitle below the article title, then uses description or a compiler-generated body excerpt for the summary. When author is present, the summary footer lists its values. Both author and categories join tags in the Home Topics area. Their entries may be ordinary strings or wiki links to public notes:
---
subtitle: Dreamers among programmers
author:
- "[[People/Ada Lovelace|Ada]]"
- Editorial team
categories:
- "[[AI]]"
- "[[Machine Learning|ML]]"
---
Wiki-link entries must be YAML double-quoted strings. Their visible label uses the alias after |, or the target note title when no alias is supplied. Every Home Topics capsule opens the matching Blog filter; a wiki-linked author shown in a post summary may additionally link directly to that public author page. On Minimal Blog, tags and categories appear together below each archive date and as filter capsules beside the publication date on the article page. Repeating the same name across tags and categories shows one topic. Unresolved wiki links produce a compiler warning and remain filterable text instead of leaking [[...]] into the site.
Content and navigation
An explicit content_type: post | doc | page wins over directory defaults. Post publication dates use date, then created, then the first Git commit. A production build rejects a post with no deterministic date. On Minimal Home and Blog, posts with pinned: true appear before unpinned posts; both groups retain reverse-chronological order. Pinning affects card presentation only, not Atom feed chronology or the previous and next post sequence.
Docs navigation follows vault directories. nav_order sorts sibling documents and nav_exclude: true removes only that note link; children remain reachable. index.md is optional at every level. When no physical index.md exists, a published exact README.md becomes the folder index. An existing index.md reserves that role even when unpublished, leaving README.md as an ordinary note. A published root index always owns /, so a different permalink on that note is rejected. A folder without a public selected index links to its first visible child after nav_order, title, and path sorting. Minimal Home can render without a root index when posts exist; if neither exists, the root redirects to the first visible top-level navigation destination. Docs redirects an indexless root to its first navigation item. Every Docs page server-renders the complete documentation tree; JavaScript navigation preserves the shared shell while replacing only page-specific content and context. Search builds its index in a Web Worker. Local graphs are projected by the compiler; the browser fetches the complete graph only after its dialog opens.
See Architecture before changing compiler or adapter seams.