Overview
The Scimax VS Code extension provides an org-publishing system for converting multiple org-mode documents into a cohesive HTML website. This is ideal for creating documentation sites, blogs, or project pages hosted on GitHub Pages.
The publishing system follows a wizard-first approach - most users can get started with a few clicks, while power users can customize everything through a JSON configuration file.
Quick Start
Step 1: Initialize a Publishing Project
Run the command Scimax: Initialize Publishing Project (scimax.publish.init) from the Command Palette to start the 5-step wizard:
Project name - A short identifier (e.g., "my-website")
Source directory - Where your org files are located (default: ./org)
Output directory - Where HTML files will be published (default: ./docs for GitHub Pages)
Publishing target - GitHub Pages (recommended) or Local/Custom
Sitemap generation - Whether to auto-generate an index page
Step 2: Add Content
Create or edit `.org' files in your source directory. Each file becomes an HTML page.
Example ./org/about.org:
#+TITLE: About This Project
#+AUTHOR: Your Name
#+DATE: 2026-01-16
* Introduction
Welcome to my project documentation!
* Features
- Feature one
- Feature two
Step 3: Publish
Run Scimax: Publish Project (scimax.publish.project) to generate HTML files,
or run scimax publish from a terminal (see CLI: Publish).
Step 4: Deploy to GitHub Pages
Commit the ./docs folder to your repository
Go to your repository's Settings → Pages
Set Source to "Deploy from a branch"
Select main branch and /docs folder
Your site will be live at https: username.github.io repo-name
Commands
These commands have no default keybindings; run them from the Command Palette.
The same publishing is available from the command line with scimax publish
(see CLI: Publish).
| Command | Keybinding | Description |
|---|---|---|
scimax.publish.init | - | Initialize publishing project |
scimax.publish.project | - | Publish selected project |
scimax.publish.file | - | Publish current file only |
scimax.publish.all | - | Publish all configured projects |
scimax.publish.preview | - | Preview published site in browser |
scimax.publish.openConfig | - | Open .org-publish.json config |
Configuration File
The publishing system supports two configuration formats:
_config.yml - YAML format (Jupyter Book compatible, takes precedence)
`.org-publish.json' - JSON format
The wizard creates a `.org-publish.json' file by default. You can also create or edit either file manually.
✅ YAML Configuration (_config.yml)
For Jupyter Book compatibility, create a _config.yml in your project root:
# _config.yml - Jupyter Book compatible
title: "My Documentation"
publish:
base_directory: "./docs"
publishing_directory: "./public"
recursive: true
auto_sitemap: true
sitemap_filename: "index.org"
html:
use_default_theme: true
toc_depth: 3
css_files:
- "./css/custom.css"
github_pages: true
A _config.yml defines a single project. Its name is the title lowercased,
with spaces replaced by hyphens (title: "My Documentation" gives the project
my-documentation; with no title it is default). Pass that name to
scimax publish <project>.
Other keys read from _config.yml:
| Key | Default | Description |
|---|---|---|
publish.base_directory | ./ | Source directory |
publish.publishing_directory | ./_build/html | Output directory |
publish.recursive | true | Process subdirectories |
publish.exclude | - | Glob pattern of files to skip (e.g. "drafts/*") |
publish.auto_sitemap | true | Generate a sitemap page |
publish.sitemap_filename | sitemap.org | Sitemap file name |
source_directory | - | Top-level alias for publish.base_directory |
publish_directory | - | Top-level alias for publish.publishing_directory |
html.use_default_theme | true | Include the built-in CSS |
html.toc_depth | - | Table of contents depth |
html.css_files | - | List of CSS files to include |
html.js_files | - | List of JavaScript files to include |
github_pages | - | Write .nojekyll (and CNAME) for GitHub Pages |
custom_domain | - | Custom domain written to CNAME |
theme | - | Site theme; see Book Theme |
publish.* keys take precedence over the top-level aliases.
JSON Configuration (`.org-publish.json')
{
"projects": {
"website": {
"baseDirectory": "./org",
"publishingDirectory": "./docs",
"recursive": true,
"autoSitemap": true
}
},
"githubPages": true
}
Full Configuration Reference
{
"projects": {
"my-website": {
"baseDirectory": "./org",
"baseExtension": "org",
"publishingDirectory": "./docs",
"recursive": true,
"exclude": "drafts/*",
"include": ["extra/special.org"],
"publishingFunction": "org-html-publish-to-html",
"autoSitemap": true,
"sitemapFilename": "index.org",
"sitemapTitle": "Home",
"sitemapStyle": "list",
"sitemapSortFiles": "alphabetically",
"sitemapSortFolders": "first",
"htmlPreamble": "./templates/nav.html",
"htmlPostamble": "./templates/footer.html",
"htmlHead": "<meta name=\"keywords\" content=\"org-mode, documentation\">",
"htmlHeadExtra": "<link rel=\"icon\" href=\"/favicon.ico\">",
"cssFiles": ["./css/style.css"],
"jsFiles": ["./js/custom.js"],
"useDefaultTheme": true,
"withAuthor": true,
"withCreator": true,
"withToc": true,
"sectionNumbers": false,
"defaultTitle": "Untitled"
}
},
"githubPages": true,
"customDomain": "docs.example.com",
"defaultProject": "my-website"
}
Configuration Options
File Selection Options
| Option | Type | Default | Description |
|---|---|---|---|
baseDirectory | string | - | Source directory containing org files |
baseExtension | string | "org" | File extension to match (without dot) |
publishingDirectory | string | - | Output directory for HTML files |
recursive | boolean | true | Process subdirectories |
exclude | string | - | Glob pattern to exclude files (e.g., "drafts/*") |
include | string[] | =[] | Extra files to include |
Publishing Function Options
| Option | Type | Default | Description |
|---|---|---|---|
publishingFunction | string | "org-html-publish-to-html" | How to process files |
Available functions:
`org-html-publish-to-html' - Convert org files to HTML
`md-html-publish-to-html' - Convert Markdown files to HTML
`ipynb-html-publish-to-html' - Convert Jupyter notebooks to HTML
`auto' - Auto-detect by file extension (default behavior)
`copy' - Copy files as-is (for static assets)
Supported File Types
The publishing system supports multiple file types:
| Extension | Description | Conversion |
|---|---|---|
.org | Org-mode documents | Full org parsing with code blocks |
.md | Markdown files | Basic Markdown to HTML conversion |
.ipynb | Jupyter notebooks | Notebook cells to HTML with syntax |
When using `auto' or `org-html-publish-to-html', files are processed based on their extension.
Sitemap Options
| Option | Type | Default | Description |
|---|---|---|---|
autoSitemap | boolean | true | Generate sitemap automatically |
sitemapFilename | string | "sitemap.org" | Sitemap file name |
sitemapTitle | string | "Site Map" | Title for sitemap page |
sitemapStyle | "list" / "tree" | "list" | Sitemap display format |
sitemapSortFiles | string | "alphabetically" | Sort order for files |
sitemapSortFolders | string | "first" | Where to place folders in listing |
Sort options for sitemapSortFiles:
`alphabetically' - Sort A-Z by title
chronologically - Oldest first (by #+DATE)
`anti-chronologically' - Newest first (for blogs)
HTML Template Options
| Option | Type | Description |
|---|---|---|
htmlPreamble | string | File path or HTML string for header/navigation |
htmlPostamble | string | File path or HTML string for footer |
htmlHead | string | Extra content for <head> section |
htmlHeadExtra | string | Additional head content (appended) |
cssFiles | string[] | CSS files to include |
jsFiles | string[] | JavaScript files to include |
useDefaultTheme | boolean | Include built-in CSS theme (default: true) |
Export Options
| Option | Type | Default | Description |
|---|---|---|---|
withAuthor | boolean | true | Include author in output |
withCreator | boolean | true | Include generator info |
withToc | boolean / number | true | Table of contents (or depth) |
sectionNumbers | boolean | false | Number sections |
defaultTitle | string | - | Default title if not specified |
Global Options
| Option | Type | Description |
|---|---|---|
githubPages | boolean | Enable GitHub Pages features |
customDomain | string | Custom domain (creates CNAME file) |
defaultProject | string | Project to publish when none specified |
Multi-Project Configuration
You can define multiple projects for different parts of your site:
{
"projects": {
"pages": {
"baseDirectory": "./org",
"publishingDirectory": "./docs",
"recursive": true,
"autoSitemap": true
},
"static": {
"baseDirectory": "./assets",
"publishingDirectory": "./docs/assets",
"baseExtension": "css|js|png|jpg|svg",
"publishingFunction": "copy"
},
"full-site": {
"components": ["pages", "static"]
}
}
}
Component Projects
A component project groups multiple projects together:
{
"full-site": {
"components": ["pages", "static", "images"]
}
}
When you publish "full-site", all component projects are published in order.
✅ Explicit Table of Contents (_toc.yml)
By default, the publishing system discovers files automatically and generates an alphabetical sitemap. For curated documentation or books where order matters, you can create an explicit table of contents using a _toc.yml file.
Overview
When a _toc.yml file exists in your source directory, the publishing system:
Publishes files in the order specified (not alphabetically)
Generates prev/next navigation links on each page
Creates an index page reflecting the TOC structure
Only includes files listed in the TOC (plus unlisted if configured)
Basic _toc.yml Format
Create a _toc.yml file in your source directory (e.g., ./docs/_toc.yml):
# Simple flat structure
format: scimax
root: index
chapters:
- file: getting-started
- file: installation
- file: configuration
- file: advanced-usage
With Sections (Nested Chapters)
format: scimax
root: index
chapters:
- file: getting-started
sections:
- file: getting-started/installation
- file: getting-started/first-steps
- file: user-guide
sections:
- file: user-guide/basics
- file: user-guide/advanced
- file: reference
With Parts (Book Format)
For larger documentation with multiple parts/volumes:
format: jb-book
root: intro
parts:
- caption: Getting Started
chapters:
- file: installation
- file: quickstart
- file: tutorials
- caption: User Guide
chapters:
- file: basics
sections:
- file: basics/syntax
- file: basics/formatting
- file: advanced
- caption: Reference
chapters:
- file: api-reference
- file: changelog
TOC Options
| Field | Description |
|---|---|
format | "scimax", "jb-book", or "jb-article" |
root | Landing page file (without extension) |
chapters | List of chapter entries |
parts | List of parts (with caption and chapters) |
Entry Options
| Field | Description |
|---|---|
file | File path without extension (relative to baseDir) |
title | Override display title (optional) |
sections | Nested child entries (optional) |
url | External URL link (optional) |
Custom Titles
You can override the title from the file's #+TITLE:
chapters:
- file: api
title: API Reference (Complete)
- file: changelog
title: Version History
External Links
Include external URLs in your TOC:
chapters:
- file: documentation
- url: https://github.com/user/repo
title: Source Code
- url: https://github.com/user/repo/issues
title: Report Issues
Combining with Configuration
Your .org-publish.json works the same way - the _toc.yml just controls ordering:
{
"projects": {
"docs": {
"baseDirectory": "./docs",
"publishingDirectory": "./public",
"autoSitemap": true,
"sitemapFilename": "index.org",
"sitemapTitle": "Documentation"
}
}
}
With ./docs/_toc.yml:
format: scimax
root: intro
chapters:
- file: getting-started
- file: user-guide
- file: reference
File Extension Resolution
The TOC references files without extensions. The system automatically finds files with these extensions (in order):
`.org'
`.md'
`.ipynb'
So file: getting-started will match getting-started.org, getting-started.md, or getting-started.ipynb.
Templates
Custom CSS
Create ./css/style.css and reference it:
{
"cssFiles": ["./css/style.css"]
}
✅ Book Theme
The book theme publishes a Jupyter Book style site: a header bar, a left
sidebar with the site navigation, a right sidebar with the page's headings,
prev/next links, client-side search, and a light/dark toggle. Turn it on in
_config.yml with a theme section that names it:
theme:
name: "book"
layout:
show_left_sidebar: true
show_right_sidebar: true
toc_depth: 3
header:
logo: "_static/logo.png"
title: "My Documentation"
navbar_links:
- text: "GitHub"
url: "https://github.com/user/repo"
footer:
copyright: "2026 Your Name"
links:
- text: "Contact"
url: "mailto:you@example.com"
appearance:
primary_color: "#0d6efd"
enable_dark_mode: true
default_mode: "auto"
search:
enabled: true
custom_css: "_static/custom.css"
theme: book on one line is the same as a theme section with only
name: book.
Requirements
Theme publishing requires a _toc.yml in the source directory (see
Explicit Table of Contents). Without it, publishing fails with
Theme-based publishing requires a _toc.yml file.
A _static/ folder in the source directory is copied to the output, so put the logo and custom CSS there.
Theme Options
| Option | Default | Description |
|---|---|---|
name | default | book for this theme; default for plain pages |
layout.show_left_sidebar | true | Show the site navigation sidebar |
layout.show_right_sidebar | true | Show the page headings sidebar (if the page has any) |
layout.toc_depth | 3 | Deepest heading level in the right sidebar |
header.logo | - | Logo image path, relative to the site root |
header.title | "Documentation" | Site title in the header |
header.navbar_links | - | List of text / url links in the header |
footer.copyright | current year | Copyright text |
footer.links | - | List of text / url links in the footer |
appearance.primary_color | "#0d6efd" | Colour of links, buttons, and the chat button |
appearance.enable_dark_mode | true | Show the light/dark toggle |
appearance.default_mode | "auto" | light, dark, or auto (follow the system) |
search.enabled | true | Build the search index and show the search box |
custom_css | - | Extra stylesheet path, relative to the site root |
chat.* | off | "Ask the docs" chat; see Docs Chat |
The defaults apply to every key you leave out, including keys missing from a
section you do set: a header section with only a logo keeps the default
title.
Docs Chat (WebLLM)
The book theme can add an "Ask the docs" chat to a published site. It finds the sections of the site that best match a question and lists them with the matching text. On request, a language model running in the reader's browser (WebLLM, on WebGPU) writes an answer from those sections. Nothing the reader types leaves their computer, and the site needs no server: it is still plain files, so it works on GitHub Pages.
✅ Turning It On
The chat is off unless theme.chat.enabled is true in _config.yml:
theme:
name: "book"
chat:
enabled: true
When it is on, publish:
adds an "Ask" button in the bottom right corner of every page, in the site's
primary_color(an icon-only circle on narrow screens)writes _static/chat-index.json, the text of every page split at its headings
copies book-chat.js, book-chat-worker.js and book-chat.css to _static/
✅ How Questions Are Answered
The question is matched against the sections with BM25 ranking (headings count more than body text, pairs of adjacent words count as phrases, and a dotted name such as
scimax.export.pdf.profilematches whole or in parts).The best 30 are reranked by a small cross-encoder model (
ms-marco-MiniLM-L-6-v2, about 23 MB, run in the browser with transformers.js) that reads the question and each section together. Its order is blended with the BM25 order, so neither one alone decides. If the reranker cannot load, the BM25 order is used.The top sections, at most two from one page, are listed right away as links, each with the part of its text that matches the question highlighted.
A "Write an answer from these sections" button gives the numbered sections to the model, which answers from them and cites them as [1], [2]. Citations become links to the sections.
A short follow-up ("and for HTML?") is searched together with the question before it, and the last two questions and answers are given to the model.
The ranking model loads by itself the first time a reader asks. The first time a reader asks for a written answer, the chat asks before downloading the language model. The browser keeps both for later visits. In a browser without WebGPU, the chat lists the matching sections without the answer button.
✅ Choosing a Model
Readers pick the model from the menu at the top of the chat panel. The smallest is the default; larger ones answer better but take longer to download and need more GPU memory. The reader's choice is remembered in the browser.
| Model | Download |
|---|---|
| SmolLM2-360M-Instruct | 380 MB |
| Llama-3.2-1B-Instruct | 880 MB |
| Qwen2.5-1.5B-Instruct | 1.6 GB |
| Llama-3.2-3B-Instruct | 2.3 GB |
The small models can be wrong, which is why the chat always shows the sections it used.
✅ Chat Options
| Key | Default | Description |
|---|---|---|
enabled | false | Add the chat and write the section index |
model | SmolLM2-360M-Instruct-q4f16_1-MLC | Model selected until the reader picks another |
models | the four models above | WebLLM prebuilt model IDs offered in the menu |
rerank | true | Rerank the search results with the cross-encoder; false uses BM25 only |
top_k | 5 | Sections given to the model with each question |
max_context_chars | 6000 | Most characters of documentation per question |
system_prompt | built in | Replaces the instructions given to the model; {site} is the header title |
title | Ask the docs | Title of the chat panel |
The model IDs are those of WebLLM 0.2.85, which the chat loads from jsDelivr.
theme:
chat:
enabled: true
model: "Llama-3.2-1B-Instruct-q4f16_1-MLC"
models:
- "Llama-3.2-1B-Instruct-q4f16_1-MLC"
- "Qwen2.5-3B-Instruct-q4f16_1-MLC"
top_k: 4
GitHub Pages Integration
When githubPages is enabled:
A `.nojekyll' file is created in the output directory (disables Jekyll processing)
If `customDomain' is set, a `CNAME' file is created
The default output directory is ./docs (GitHub Pages convention)
The default sitemap becomes `index.org' → `index.html'
Custom Domain Setup
To use a custom domain:
{
"githubPages": true,
"customDomain": "docs.example.com"
}
Then configure your DNS to point to GitHub Pages.
Cross-File Links
Links between org files are automatically converted to HTML links:
See [[file:other-page.org][the other page]] for details.
Becomes:
See <a href="other-page.html">the other page</a> for details.
Workflow Examples
Documentation Site
{
"projects": {
"docs": {
"baseDirectory": "./docs-src",
"publishingDirectory": "./docs",
"autoSitemap": true,
"sitemapFilename": "index.org",
"sitemapTitle": "Documentation",
"withToc": 2
}
},
"githubPages": true
}
Blog
{
"projects": {
"blog": {
"baseDirectory": "./posts",
"publishingDirectory": "./docs",
"autoSitemap": true,
"sitemapFilename": "index.org",
"sitemapTitle": "Blog",
"sitemapSortFiles": "anti-chronologically",
"htmlPreamble": "./templates/blog-header.html"
}
},
"githubPages": true
}
Project with Static Assets
{
"projects": {
"pages": {
"baseDirectory": "./org",
"publishingDirectory": "./docs",
"autoSitemap": true
},
"images": {
"baseDirectory": "./images",
"publishingDirectory": "./docs/images",
"baseExtension": "png|jpg|gif|svg",
"publishingFunction": "copy"
},
"css": {
"baseDirectory": "./css",
"publishingDirectory": "./docs/css",
"baseExtension": "css",
"publishingFunction": "copy"
},
"site": {
"components": ["pages", "images", "css"]
}
},
"githubPages": true,
"defaultProject": "site"
}
Troubleshooting
"No publishing configuration found"
Run Scimax: Initialize Publishing Project (scimax.publish.init), or scimax publish --init from a terminal, to create a .org-publish.json file.
"Base directory does not exist"
Create the source directory specified in `baseDirectory', or update the config to point to an existing directory.
Links not working after publish
Ensure you're using file: links to reference other org files:
[[file:other.org][Link text]]
GitHub Pages not updating
Ensure the ./docs folder is committed
Check that GitHub Pages is configured to serve from /docs
Wait a few minutes for GitHub to rebuild the site