gfm-it
v0.4.2
Published
Render GitHub Flavored Markdown into a complete HTML document.
Readme
gfm-it
Render GitHub Flavored Markdown into a complete HTML document.
The package ships both the renderer and the GFM assets. Use local assets when a server exposes /asset/<key>, and inline assets when the HTML should stand alone.
CLI
# CLI defaults to inline assets, so the output HTML can be opened directly.
gfm-it README.md --title README --output README.html
# SEO metadata.
gfm-it post.md \
--canonical https://example.com/post \
--fallback-image true \
--output post.html
# Use server-hosted assets instead of inline assets.
gfm-it post.md --asset-mode local --asset-base-url /asset/ --output post.html
# Use CDN assets.
gfm-it post.md --asset-mode remote --output post.html
# Use a stylesheet href directly.
gfm-it post.md --asset-mode remote -c https://example.com/hi.css?raw=true --output post.html
# Select a bundled GFM theme. Short aliases such as `github` are accepted.
gfm-it post.md -c github --output post.html
# Markdown front matter `gfm_css` overrides -c/--css when it is valid.
# Read from stdin.
printf '# Hello\n' | gfm-it --title HelloJavaScript
import { renderMarkdownToHtml } from 'gfm-it';
const html = renderMarkdownToHtml('# Hello', {
title: 'Hello',
canonical: 'https://example.com/hello',
fallbackImage: true,
// API default: remote. CLI default: inline.
assetMode: 'remote',
});const html = renderMarkdownToHtml(markdown, {
// Self-contained HTML: CSS and JS are inserted as <style> and <script>.
assetMode: 'inline',
});const html = renderMarkdownToHtml(markdown, {
// Compatible with servers that expose packaged assets at /asset/<key>.
assetMode: 'local',
assetBaseUrl: '/asset/',
// Raw HTML insertion points around the generated article.
slots: {
headEnd: '<meta name="robots" content="index,follow">',
bodyStart: '<!-- hint: append ?raw to view the raw file -->',
articleBefore: '<nav><a href="/">Home</a></nav>',
articleAfter: '<hr>',
bodyEnd: '<script>console.log("done")</script>',
},
extraCss: '.markdown-body { scroll-margin-top: 2rem; }',
bodyClass: 'post-page',
footerHtml: '<a href="/">Home</a>',
});const html = renderMarkdownToHtml(markdown, {
// Resolver wins over assetMode and assetBaseUrl.
resolveAssetUrl(asset) {
return `/static/gfm/${asset.key}`;
},
});Go
package main
import gfmit "github.com/mirtlecn/gfm-it"
func render(markdown string) (string, error) {
return gfmit.RenderMarkdownToHTML(markdown, gfmit.RenderOptions{
Title: "Hello",
Canonical: "https://example.com/hello",
FallbackImage: true,
// Go API default: remote.
AssetMode: "remote",
})
}html, err := gfmit.RenderMarkdownToHTML(markdown, gfmit.RenderOptions{
// Match a server route such as /asset/vercel.gfm.css.
AssetMode: "local",
AssetBaseURL: "/asset/",
Slots: gfmit.RenderSlots{
HeadEnd: `<meta name="robots" content="index,follow">`,
BodyStart: `<!-- hint: append ?raw to view the raw file -->`,
},
ExtraCSS: `.markdown-body { scroll-margin-top: 2rem; }`,
BodyClass: "post-page",
FooterHTML: `<a href="/">Home</a>`,
})html, err := gfmit.RenderMarkdownToHTML(markdown, gfmit.RenderOptions{
// Self-contained HTML.
AssetMode: "inline",
})html, err := gfmit.RenderMarkdownToHTML(markdown, gfmit.RenderOptions{
// Resolver wins over AssetMode and AssetBaseURL.
ResolveAssetURL: func(asset gfmit.Asset) (string, error) {
return "/static/gfm/" + asset.Key, nil
},
})Assets
Files under assets/ are shipped in their source format. Remote mode points at jsDelivr .min.css and .min.js URLs, and embedded/inline assets are minified during the build without rewriting the source files. Asset keys are generated from the source file name, for example assets/github.gfm.css becomes github.gfm.css.
import {
assets,
getAsset,
getAssetPath,
getAssetContentType,
getAssetRemoteUrl,
} from 'gfm-it';
import { getEmbeddedAssetContent } from 'gfm-it/embedded';
console.log(getAsset('ravel.gfm.css'));
console.log(getAssetRemoteUrl('gfm-addons.js'));
console.log(getEmbeddedAssetContent('highlight-core.js'));asset, ok := gfmit.GetAsset("ravel.gfm.css")
content, asset, err := gfmit.ReadAsset("ravel.gfm.css")
embeddedContent, err := gfmit.ReadEmbeddedAssetContent("ravel.gfm.css")
allAssets := gfmit.Assets()Metadata
---
title: YAML Title
description: Short summary for search previews.
canonical: https://example.com/posts/yaml-title
cover: https://example.com/cover.png
date: 2026-06-10
update: 2026-06-11T10:20:30Z
gfm_css: folio
---
# Visible Heading
Article body.renderMarkdownToHtml(markdown, {
// Priority: option title > YAML title > first Markdown heading > empty.
title: 'Option Title',
// Priority: option canonical > YAML canonical.
canonical: 'https://example.com/override',
// If no YAML cover/image or absolute Markdown image exists,
// emit a stable grayscale Picsum social image.
fallbackImage: true,
});| Output | Source |
| --- | --- |
| <title> | option title, YAML title, first heading |
| canonical / og:url | option canonical, YAML canonical |
| description / og:description | YAML description, YAML summary, paragraph/list body text |
| og:image / twitter:image | YAML cover, YAML image, first absolute Markdown image, fallback image |
| article:published_time | YAML date |
| article:modified_time | YAML update |
| main CSS | valid YAML gfm_css, then option css |
gfm_css accepts the same CSS choices as remote-mode css: bundled GFM theme short names such as github, folio, and terminal, remote stylesheet URLs ending in .css before any query string, and local stylesheet hrefs such as /theme.css, ./theme.css, ../theme.css, github.gfm.css, or ../css.
If gfm_css is invalid, the renderer ignores it and falls back to the API or CLI css option. Short-name theme values still follow the selected assetMode: remote uses jsDelivr, local uses assetBaseUrl, and inline embeds the minified bundled asset. Href values are emitted as direct stylesheet links.
Options
renderMarkdownToHtml(markdown, {
title: '',
canonical: '',
fallbackImage: false,
// Valid YAML gfm_css overrides this option.
// Built-in themes accept short names only: ravel, whitey, newsprint, github, folio, terminal, vercel.
// Values ending in .css are treated as stylesheet hrefs, not built-in themes.
// Remote mode also accepts stylesheet hrefs.
css: 'vercel',
assetMode: 'remote', // remote | local | inline
assetBaseUrl: '/asset/',
resolveAssetUrl: undefined,
slots: {},
extraCss: '',
bodyClass: '',
footerHtml: '',
});gfmit.RenderOptions{
Title: "",
Canonical: "",
FallbackImage: false,
// Valid YAML gfm_css overrides this option.
// Built-in themes accept short names only: ravel, whitey, newsprint, github, folio, terminal, vercel.
// Values ending in .css are treated as stylesheet hrefs, not built-in themes.
// Remote mode also accepts stylesheet hrefs.
CSS: "vercel",
AssetMode: "remote", // remote | local | inline
AssetBaseURL: "/asset/",
ResolveAssetURL: nil,
Slots: gfmit.RenderSlots{},
ExtraCSS: "",
BodyClass: "",
FooterHTML: "",
}Dynamic assets:
| Trigger | Assets |
| --- | --- |
| always | selected base CSS; gfm-addons.css, gfm-addons.js |
| code block | highlight light/dark CSS; Go also injects highlight-core.js |
| display math | KaTeX CSS |
Go uses goldmark, GFM, footnotes, GitHub alert callouts, KaTeX, and unsafe raw HTML rendering. The wrapper options match the JavaScript API; parser output is not guaranteed to be byte-for-byte identical.
