Docs Writing
How a post is built
The path from a Ghost draft to rendered HTML, and the four things that happen to your markup on the way.
Posts are written in Ghost. Ghost is the editor and the database; it never serves
a reader. At build time this site pulls post.html — Ghost’s own rendering of
the Koenig editor — and runs it through one transform before it reaches a page.
That transform is where everything on the rest of these pages happens.
The pipeline
transformPostDirectives(html, context) in
src/features/posts/server/directives/index.ts runs four passes, in this order.
1. Embed enrichment. Bare <iframe> embeds and shortcodes are rewritten
into this site’s own components: Apple Music iframes become the listening card,
YouTube iframes become the click-to-load facade, [mood:123] becomes a mood
embed. This pass only runs for rich output targets — see below.
2. Masking. Everything inside <code>, <pre>, <script> and <style> is
lifted out and replaced with a private-use-area token. Nothing in the following
passes can see it, so a fenced code block showing [!poem] stays a code block.
The tokens are put back at the very end, and if a directive somehow mangled one
the transform throws rather than shipping broken markup.
3. Block and meta directives. Any paragraph whose entire content is a
[!name key=value] marker is matched. A block directive replaces the paragraph
with rendered HTML; a meta directive is removed from the body and its
attributes handed to the page instead.
4. Inline directives. These get the whole document rather than one paragraph,
because what they match is not a marker. poem reshapes blockquotes, footnotes
rewrites [^label] references and collects the definitions into a list at the
foot of the post.
Anything still looking like [!something] after all that is reported as an
unknown directive.
Output targets
The same post is rendered for more than one destination, and a YouTube player is useless in an RSS reader. Every directive is handed the target and decides what to emit.
| Target | Used for | Embeds |
|---|---|---|
web |
The /blog/[slug] page |
Full interactive components |
preview |
/dev/blog/[id] draft preview |
Full interactive components |
rss |
/blog/rss.xml |
Plain links, absolute URLs |
agent-markdown |
Accept: text/markdown responses |
Plain links, absolute URLs |
og |
Open Graph image text | Text only |
excerpt |
List and card summaries | Text only |
web and preview are the rich targets. Everywhere else, a [!music]
directive degrades to “Listen on Apple Music” and a footnote reference becomes an
inline parenthetical rather than a superscript pointing at an anchor that the
consumer cannot follow.
Warnings, not failures
A malformed directive does not break the build. It is skipped, the marker is dropped, and a warning is printed:
[blog-directive:invalid-directive-attributes] Invalid "mood" directive in post "my-post": attribute "id" must be a positive integer.
Watch the build output when you publish. The codes you will see are
unknown-directive, invalid-directive-attributes, invalid-directive-content,
and the four footnote codes.
There is exactly one exception. An unrecognised model in [!authors] throws and
stops the build, because a typo there would silently drop an authorship credit
off a published post — which is the one failure mode worth being loud about.