Org-Mode Include Patterns for Long Research Pages
Table of Contents
1. Problem
A single index.org with case studies, diagrams, and data tables hits 500+ lines. Editing becomes slow, the narrative is hard to navigate, and adding a new case study means touching the entire file.
2. Solution: #+INCLUDE with subdirectory
Split case studies (or any large section) into separate .org files
under an includes/ subdirectory. The parent index.org uses
#+INCLUDE to pull them in at publish time.
research/my-topic/
index.org # 80 lines -- thesis, #+INCLUDEs, resources
includes/
case-001-first.org # 45 lines
case-002-second.org # 175 lines
case-003-third.org # (add later -- one file, one #+INCLUDE line)
diagram-foo.png
diagram-bar.png
data/
measurements.csv
In index.org:
* Case Study: First #+INCLUDE: "includes/case-001-first.org" * Case Study: Second #+INCLUDE: "includes/case-002-second.org"
3. Image Path Resolution
Built a minimal #+INCLUDE test case (index.org + includes/case.org containing [[file:../diagram.png]]) and ran `emacs --batch -l org --eval '(org-html-export-to-html)'`. The exported <img src> resolved to the file sitting next to index.org, confirming ../ inside an include resolves from the include file's own directory, not index.org's -- matching this section's claim. Cross-checked against production usage: site/research/2026-reversible-pipeline-transforms/includes/taxonomy.org and four other includes/ files use this exact ../ pattern live.
The critical gotcha: #+INCLUDE changes the base directory for
[[file:...]] references. An included file's [[file:diagram.png]]
resolves relative to the include file's location, not the parent.
3.1. Rule
Images live in the parent directory (next to index.org). Include
files reference them with ../:
;; In includes/case-001-first.org: [[file:../diagram-foo.png]] ← resolves to parent dir [[file:../data/measurements.csv]] ← resolves to parent/data/
3.2. Why this works for both editing and publishing
- Editing the include file directly: Emacs resolves
../fromincludes/, which points to the parent directory. Inline image display (C-c C-x C-v) works. - Publishing via #+INCLUDE: org-publish expands the include into
index.org's context. The../in the HTML output resolves from the includes subdirectory on the server, pointing back to the parent where the images are deployed. - Editing index.org: The includes are opaque until expanded. Use
C-c 'on the #+INCLUDE line to open the included file.
4. Heading Levels
Same batch-export test as [[*Image Path Resolution]]: an include starting with =**= (no =*= heading of its own) exported as an <h3> nested under the parent's <h2>, confirming #+INCLUDE preserves the included file's heading levels as written rather than rebasing them.
#+INCLUDE preserves heading levels from the included file. If your
include starts with ** subheadings (because the * heading is in
the parent), the structure is correct. If it starts with *, it
becomes a peer of the parent's heading.
;; In index.org: * Case Study: Storm ← * heading in parent #+INCLUDE: "includes/case-002-storm.org" ;; In includes/case-002-storm.org: On May 19, 2026... ← body text, no * heading ** The Signal ← ** subheading (correct level) ** Holding Patterns ← ** subheading
5. Cross-references Between Includes
[[*Heading]] between includes resolves in the assembled index.html; it broke only on standalone fragment pages, now excluded
Within the published page, an include can link to a heading in another
include with [[*Heading]] or [[#custom-id]]. Both resolve, because
#+INCLUDE inlines every fragment before export. While editing, each
include is still self-contained: Emacs can't follow those links from the
fragment alone.
From outside the page, link to the parent, never to the fragment:
[[file:../topic/index.org::#custom-id]], not
[[file:../topic/includes/x.org]]. Fragments don't publish as pages of
their own; see 6.
6. What Gets Published
Confirmed against current source: all three :exclude regexes in publish.el contain \(^\|/\)includes/ (grep); src/wal_sh/site/sitemap.clj excludes #"/includes/" and src/pocket_es/indexer.clj skips "includes" via its skip-dirs set, matching the claimed equivalent rule. `git log` on publish.el confirms the prior rule was ^includes/ until commit b7b4c47e (2026-10-08 09:42:37 -0400, "fix(publish): exclude nested includes/ fragments from standalone publish (#207)"), matching the dated claim.
Only index.org becomes a page. The publish :exclude in publish.el
matches \\(^\\|/\\)includes/, which is the same rule the sitemap
generator and the pocket-es indexer already used. Fragments therefore
never appear as standalone .html or .md pages, in the sitemap, or in
search. Until 2026-10-08 the publish rule was ^includes/, which
matched only site/includes/, so fragments under research notes leaked
out as untitled pages. The experiment and the fix are recorded in
Org-Publish at Scale.
Images and data files under includes/ still ship. The
wal.sh-research-images attachment project publishes them, and it does
not use this exclude.
A separate assembled source file such as source.org is unnecessary:
index.org plus its includes already is the source. If you need one
flat file, org-org-publish-to-org produces it. Flat NN-x.org
siblings next to index.org are the one layout to avoid, because each
sibling publishes as a page of its own.
7. When to Split
Split when:
- A section exceeds ~100 lines
- The section is a self-contained case study or analysis
- You expect to add more sections of the same type later
- Multiple people (or agents) edit different sections concurrently
Don't split:
- Sections under 50 lines
- Tightly coupled content that references adjacent sections by line
- Tables of contents (the parent owns the ToC)
8. Naming Convention
includes/case-NNN-slug.org for numbered case studies. The number
controls ordering; the slug is human-readable. Examples:
case-001-baw48.orgcase-002-storm.orgcase-003-winter-storm.orgsection-architecture.orgsection-methodology.org