Build lifecycle & hook order
This page is the visual reference for where things hook into a Netdocs build. If you are writing a plugin or a theme override and you are not sure when your code runs — or where in the pipeline you should hook in — start here.
Everything below is driven by
BuildEngine.BuildAsync,
which orchestrates one build from config to emitted HTML.
The pipeline at a glance
flowchart TD
A[Load appsettings.json to SiteConfig] --> B[Load external plugins
plugins/*.dll]
B --> C[Build plugin host
IPlugin.Configure on every plugin]
C --> D[Discover docs/**
ContentDiscovery]
D --> E{INavigationFilter
ShouldInclude}
E -->|kept| F[IBuildHook.OnBuildStartAsync]
F --> G[IContentGenerator.GenerateAsync
virtual pages: blog, tags, archives]
G --> H[IMarkdownPreprocessor.ProcessAsync
ordered by Order — snippets, abbr, macros]
H --> I[Parse + render Markdown
Markdig pipeline + IMarkdigContributor.Extend
parallel, render-cache aware]
I --> J[Resolve navigation
NavigationBuilder]
J --> K[Template render
Scriban + theme, parallel to HTML]
K --> L[Emit 404.html]
L --> M[IBuildHook.OnPageRenderedAsync
per page]
M --> N[Copy assets
theme + docs static + plugin-registered]
N --> O[IBuildHook.OnBuildCompleteAsync
search index, rss, tags.json]
O --> P[Emit sitemap.xml]
P --> Q[Prune stale output files]
classDef hook fill:#e8f0fe,stroke:#3f51b5,color:#1a237e;
class E,F,G,H,I,M,O hook;
The blue nodes are the extension points a plugin can implement. Everything else is engine-owned.
Ordered stages
| # | Stage | Extension point | Notes |
|---|---|---|---|
| 1 | Load configuration | — | appsettings.json becomes SiteConfig. |
| 2 | Load external plugins | (discovery) | plugins/*.dll are registered but not yet enabled. See External plugins. |
| 3 | Configure plugins | IPlugin.Configure |
Called once per enabled plugin. Register assets/scripts/services here. Runs in appsettings.json plugin order. |
| 4 | Discover content | — | Walks docs/**, honoring .mkdocsignore / exclude. |
| 5 | Navigation filters | INavigationFilter.ShouldInclude |
A page is kept only if all filters return true. |
| 6 | Build start | IBuildHook.OnBuildStartAsync |
First async hook. The full (filtered) page set is on site.Pages. |
| 7 | Content generation | IContentGenerator.GenerateAsync |
Emit virtual pages (blog lists, tag pages). Generated pages join site.Pages. |
| 8 | Preprocess Markdown | IMarkdownPreprocessor.ProcessAsync |
Runs in ascending Order for every page (including generated). Text-in/text-out. |
| 9 | Parse + render | IMarkdigContributor.Extend |
Markdig extensions are contributed once; pages render in parallel and results are cached by content hash. |
| 10 | Resolve navigation | — | Builds the nav tree used by templates. |
| 11 | Template render | (theme templates) | Scriban renders each page (parallel) to HTML. See Template render order. |
| 12 | 404 page | (theme templates) | 404.html is rendered if the theme provides it. |
| 13 | Page rendered | IBuildHook.OnPageRenderedAsync |
Called for every page after HTML is written. Good for indexing (search). |
| 14 | Copy assets | IPluginContext.AddAsset |
Theme assets, docs/ static files, and plugin-registered assets are copied. |
| 15 | Build complete | IBuildHook.OnBuildCompleteAsync |
Last hook. Emit whole-site artifacts (search index, RSS, tag exports, social cards). |
| 16 | Sitemap | — | Built-in sitemap.xml. |
| 17 | Prune | — | Removes output files this build did not (re)produce. |
Preprocessor ordering
Preprocessors are sorted by their Order property (ascending). The built-ins use:
| Order | Preprocessor | Why |
|---|---|---|
| 10 | snippets |
Expand --8<-- includes first so later steps see the full text. |
| 20 | table-reader / abbreviations |
Operate on already-included content. |
| 25 | macros |
Runs after snippets/table-reader so their output can contain macros. |
Pick an Order relative to these when you need your preprocessor to run before or after
a built-in.
Overriding order from config
You don't need to recompile a plugin to change where it runs: set an order on its
plugins entry to override its natural Order. Lower runs earlier; ties keep config
order. For example, to run macros before table-reader:
{ "name": "macros", "order": 15 }
Other hook types (Markdig contributors, content generators, build hooks, navigation
filters) run in the order their plugins are listed in plugins, so reordering that list
is all that's needed for them.
Template render order
Stage 11 renders each page with Scriban. The theme's
main.html is the root layout; it pulls in partials in roughly this order:
flowchart TD
M[main.html] --> H[partials/header.html]
M --> N[partials/nav.html]
M --> T[partials/toc.html]
M --> C[page.content — rendered Markdown]
M --> PM[partials/post-meta.html
blog posts]
M --> BN[partials/blog-nav.html
blog views]
M --> F[partials/footer.html]
M --> S[partials/search.html]
M --> HL[partials/highlight.html]
M --> ME[partials/mermaid.html]
M --> TB[partials/tabs.html]
To override any of these, point theme.custom_dir at a folder of Scriban templates
that mirror the theme layout. (Material's Jinja2 overrides are detected and ignored.) See
Theme reference.
Choosing where to hook
| I want to… | Implement | Runs at |
|---|---|---|
| Register CSS/JS/assets or read options | IPlugin.Configure |
Stage 3 |
| Include/exclude pages | INavigationFilter |
Stage 5 |
| Do setup once the page set is known | IBuildHook.OnBuildStartAsync |
Stage 6 |
| Create new pages | IContentGenerator |
Stage 7 |
| Rewrite Markdown text | IMarkdownPreprocessor |
Stage 8 |
| Add Markdown syntax/extensions | IMarkdigContributor |
Stage 9 |
| React to each rendered page | IBuildHook.OnPageRenderedAsync |
Stage 13 |
| Emit a whole-site artifact | IBuildHook.OnBuildCompleteAsync |
Stage 15 |
See the Events & callbacks reference for the exact method signatures and examples.