Custom Syntax Highlighter
On this page, you’ll learn how to create and register a custom syntax highlighter.
Overview
A syntax highlighter controls how source blocks are rendered.
Asciidoctor.js ships with a highlight.js adapter, but you can provide your own implementation by extending SyntaxHighlighterBase.
There are two usage patterns:
- Server-side highlighting
-
The highlighter processes the source at conversion time. Override
handlesHighlighting()→trueand implementhighlight(). - Client-side highlighting
-
The highlighter injects scripts or stylesheets into the output document. Override
hasDocinfo()→trueand implementdocinfo().
Both patterns can also override format() to control the <pre><code> wrapper.
Server-side highlighting
In this pattern, the highlight() method receives the raw source text and returns the highlighted markup.
import { SyntaxHighlighterBase } from '@asciidoctor/core'
class UpperCaseHighlighter extends SyntaxHighlighterBase { (1)
handlesHighlighting() {
return true (2)
}
highlight(node, source, lang, opts) {
return source.toUpperCase() (3)
}
}
| 1 | Extend SyntaxHighlighterBase to inherit the default format() implementation. |
| 2 | Return true to tell Asciidoctor.js that this highlighter processes source at conversion time. |
| 3 | source is the raw source text; return the highlighted markup as a plain string.
Return a [string, number] tuple if the source shifts by one or more lines (e.g. line numbers are prepended). |
Client-side highlighting
In this pattern, the highlighter injects a script or stylesheet into the output document and leaves the source block as-is for the browser to process.
import { SyntaxHighlighterBase } from '@asciidoctor/core'
class PrismHighlighter extends SyntaxHighlighterBase {
hasDocinfo(location) {
return location === 'footer' (1)
}
docinfo(location, doc, opts) {
return '<script src="https://cdn.example.com/prism.js"></script>' (2)
}
}
| 1 | Return true for the location slots where this highlighter needs to inject markup ('head' or 'footer'). |
| 2 | Return the HTML markup to inject at the given location. |
Customising the <pre><code> wrapper
Both patterns use the format() method to wrap the source in <pre><code> tags.
The base class provides a default implementation; override it to change the wrapper.
format() may return a plain string or a Promise<string> — the caller always `await`s the result.
import { SyntaxHighlighterBase } from '@asciidoctor/core'
class MinimalHighlighter extends SyntaxHighlighterBase {
async format(node, lang, opts) {
const content = await node.content() (1)
return `<pre><code>${content}</code></pre>` (2)
}
}
| 1 | Call node.content() to get the (possibly highlighted) source with all substitutions applied. |
| 2 | Return the final HTML wrapping the source block. |
Registering a custom syntax highlighter
Per-conversion override with syntax_highlighters
Pass a syntax_highlighters map to load() or convert() to override a specific highlighter name for a single conversion.
Setting a name to null disables that highlighter.
import { load } from '@asciidoctor/core'
const doc = await load(input, {
safe: 'safe',
syntax_highlighters: { 'my-hl': UpperCaseHighlighter }, (1)
attributes: { 'source-highlighter': 'my-hl' },
})
| 1 | Map the name used in :source-highlighter: to your class.
Other highlighters (e.g. highlightjs) remain available. |
To disable a highlighter:
const doc = await load(input, {
safe: 'safe',
syntax_highlighters: { 'highlightjs': null }, (1)
attributes: { 'source-highlighter': 'highlightjs' },
})
// doc.syntaxHighlighter === null
| 1 | Setting a name to null prevents the highlighter from being resolved. |
Global registration with a custom factory
For full control over highlighter resolution, pass a syntax_highlighter_factory instance.
import { load, CustomFactory } from '@asciidoctor/core'
const factory = new CustomFactory({ 'my-hl': UpperCaseHighlighter }) (1)
const doc = await load(input, {
safe: 'safe',
syntax_highlighter_factory: factory,
attributes: { 'source-highlighter': 'my-hl' },
})
| 1 | CustomFactory takes a seed registry as a plain object.
Only the names registered in this factory are available — built-in adapters are not inherited.
Use syntax_highlighters instead if you want to keep built-in adapters available. |