Partials
Partials let you extract repeated content into separate files and include them anywhere. Instead of duplicating the same call-to-action, disclaimer, or author bio across multiple pages, write it once and reference it by name.
Partials use Markdoc's built-in {% partial %} tag. Content is resolved at build time, so there is no runtime overhead.
A partial cannot contain {% data %} or {% snippet %}. Those resolve in an earlier build phase than partials do, and the build stops with an error naming the fix — use {% include %}, refrakt's sibling rune, which reads the same _partials/ directory. See Partial or include? below.
Setup
Create a _partials/ directory at the root of your content directory. Each .md file inside becomes a named partial.
content/
├── _partials/
│ ├── cta.md
│ └── shared/
│ └── disclaimer.md
├── docs/
│ └── getting-started.md
└── _layout.md
Partial files are plain Markdown — no frontmatter required. They can contain any Markdoc content including runes.
**Ready to get started?** [Sign up today](/signup) and join thousands of happy users.
Basic usage
Use the {% partial %} tag with a file attribute pointing to the filename relative to _partials/. The tag is self-closing.
{% partial file="cta.md" /%}
The partial's content is inserted directly into the page at that position. It behaves as if you had written the content inline.
Subdirectories
Organize partials into subdirectories for larger sites. Reference them with the relative path from _partials/.
{% partial file="shared/disclaimer.md" /%}
Namespaced partials via file roots
In addition to the site-local _partials/ directory, refrakt supports file roots — named directories declared in refrakt.config.json (or by plugins) that any page can reach via a namespace:filename syntax.
This is the right tool when:
- A monorepo has multiple sites that share chrome (the same footer / disclaimer / CTA across
docs/,marketing/, andblog/). - A plugin ships content fragments the user's pages need to embed.
- Legal text, examples, or generated content lives outside any site's content tree.
User configuration
Register file roots in refrakt.config.json. Keys are namespace names; values are paths relative to the config file's directory (the project root).
{
"sites": { "docs": { /* … */ }, "marketing": { /* … */ } },
"fileRoots": {
"shared": "_shared-partials",
"legal": "../legal-snippets"
}
}
Reference them with the namespace prefix:
{% partial file="shared:footer.md" /%}
{% partial file="legal:terms.md" /%}
{# Subdirectories within a file root work too #}
{% partial file="shared:hero/welcome.md" /%}
Plugin-registered file roots
Plugins can ship their own file roots via the Plugin.fileRoots field. Once the plugin is installed, its namespace is reachable from any page in the project — no user configuration needed.
import type { Plugin } from "@refrakt-md/types";
export const plugin: Plugin = {
name: "@refrakt-md/plan",
// ... runes, theme, pipeline ...
fileRoots: {
plan: "../../plan", // relative to the plugin package's own directory
},
};
A user site that depends on the plan plugin can then write {% partial file="plan:SPEC-066-expand-rune.md" /%} and have the spec embedded directly.
Resolution rules
| Input | Resolution |
|---|---|
footer.md (no colon) | Site-local _partials/footer.md. |
shared:footer.md | Look up shared in the merged file-roots map; resolve footer.md from that directory. |
unknown:foo.md | Build error: namespace not registered; the error lists the available namespaces. |
shared:missing.md | Build error: file not found in the named root. |
shared:../escape.md | Build error: path escapes the named root. |
shared:/abs.md | Build error: absolute paths not permitted in namespaced references. |
Collision rules
- User config wins if both user config and a plugin register the same namespace. The plugin's contribution is silently dropped, with a dev-time warning naming the namespace.
- Plugin vs. plugin collisions are a hard error at plugin load (plugins should pick distinct namespace names).
- The namespace
siteis reserved for future site-level resolution and cannot be used.
Passing variables
Pass data into a partial using the variables attribute. Inside the partial, access variables with the $ prefix.
Partial file (_partials/greeting.md):
Welcome, {% $name %}! You're on the **{% $plan %}** plan.
Usage:
{% partial file="greeting.md" variables={name: "Sarah", plan: "Pro"} /%}
This renders as: "Welcome, Sarah! You're on the Pro plan."
Nesting partials
Partials can include other partials. A shared header partial might pull in a logo partial and a navigation partial.
{% partial file="logo.md" /%}
{% partial file="nav-links.md" /%}
Avoid circular references — a partial that includes itself (directly or indirectly) will cause a parse error.
How it works
Each partial file is parsed once per build into a Markdoc syntax tree. When the page is transformed, Markdoc looks up {% partial %}'s file in that set and transforms the partial's tree in its place, with variables bound as a scope. This means:
- No extra network requests or runtime cost
- Headings in partials appear in the table of contents
- Runes inside partials work as they would inline — except the two that resolve before the transform, below
Partial or include?
refrakt resolves {% data %} and {% snippet %} in a preprocess phase that runs before the transform, walking the page's syntax tree. A partial's content is not in that tree yet, so a preprocessor rune inside one is never seen, survives to its own transform, and stops the build:
{% data %} reached the transform phase unresolved.
If this file is pulled in with {% partial %}: use {% include %} instead.
{% include %} is refrakt's answer. It pastes the file's content into the page ahead of the preprocess phase, so data and snippet inside it resolve. It reads the same _partials/ directory and the same file roots, and takes the same variables — only the tag name changes.
Reach for {% partial %} first. It is Markdoc's, it behaves the same way in every Markdoc project, and it covers the common case. Reach for {% include %} when the file contains {% data %} or {% snippet %}.
partial | include | |
|---|---|---|
| Whose tag | Markdoc's | refrakt's |
Reads _partials/ and file roots | yes | yes |
Takes variables | yes | yes |
Contains data / snippet | no — the build stops | yes |
| Sees the page's own variables | no — its scope replaces them | yes, for anything it does not bind |
Editor support
The Refrakt language server provides full IDE support for partials:
- Completions — type
{% partial file="and get autocomplete suggestions for available partial files - Diagnostics — a warning appears if a referenced partial file does not exist, with suggestions for similar names
- Go-to-definition — jump directly to the partial source file from any
{% partial %}tag