Skip to main content
Asciidoctor’s extension system lets you hook into every stage of the processing pipeline — from raw source lines all the way to the final output string. Extensions are written in Ruby and registered either globally or per document.

Processing pipeline

Extensions participate in processing in a defined order:
1

Preprocessor

Runs after source lines are normalized but before parsing begins. Can modify or replace source lines. See Preprocessor.
2

IncludeProcessor

Intercepts include::target[] directives. The first processor that claims a target handles it.
3

Parser (BlockProcessor / BlockMacroProcessor)

The parser builds the abstract syntax tree (AST). Custom delimited blocks and block macros are handled by registered BlockProcessor and BlockMacroProcessor instances. See Block processor.
4

TreeProcessor

Runs on the fully-parsed AST. Ideal for analysis, transformation, or adding nodes. See Tree processor.
5

Conversion + InlineMacroProcessor

Conversion begins; inline markup is processed. Custom inline macros (name:target[attrs]) are handled by InlineMacroProcessor instances. See Inline macro processor.
6

Postprocessor

Receives the converted output as a String and can modify it before it is written. See Postprocessor.

Registering extensions

You can register extensions globally so they apply to every document, or pass a registry as a per-document option.
require 'asciidoctor'
require 'asciidoctor/extensions'

Asciidoctor::Extensions.register do
  preprocessor     MyPreprocessor
  include_processor MyIncludeProcessor
  tree_processor   MyTreeProcessor
  postprocessor    MyPostprocessor
  block            MyBlockProcessor
  block_macro      MyBlockMacroProcessor
  inline_macro     MyInlineMacroProcessor
end
Globally registered extensions apply every time Asciidoctor.convert or Asciidoctor.load is called. Remove all global registrations with:
Asciidoctor::Extensions.unregister_all

Minimal complete example

The following snippet registers one extension of each major type and converts a document:
require 'asciidoctor'
require 'asciidoctor/extensions'

class HelloPreprocessor < Asciidoctor::Extensions::Preprocessor
  def process document, reader
    reader.lines.unshift 'NOTE: Preprocessed.'
    reader
  end
end

class ShoutBlock < Asciidoctor::Extensions::BlockProcessor
  use_dsl
  named :shout
  on_context :paragraph
  parse_content_as :simple

  def process parent, reader, attrs
    create_paragraph parent, reader.lines.map(&:upcase), attrs
  end
end

class BtnMacro < Asciidoctor::Extensions::InlineMacroProcessor
  use_dsl
  named :btn

  def process parent, target, attrs
    create_inline parent, :quoted, %(<kbd>#{target}</kbd>), type: :unquoted
  end
end

Asciidoctor::Extensions.register do
  preprocessor HelloPreprocessor
  block        ShoutBlock
  inline_macro BtnMacro
end

puts Asciidoctor.convert(<<~ADOC)
  = Demo

  [shout]
  hello world

  Press btn:[Enter] to continue.
ADOC

Asciidoctor::Extensions.unregister_all

Extension types

Preprocessor

Modify source lines before parsing.

Include processor

Intercept include::target[] directives to serve content from custom sources.

Block processor

Handle custom delimited blocks and paragraphs.

Block macro processor

Handle custom block-level macros of the form name::target[attrs].

Inline macro processor

Handle custom inline macros of the form name:target[attrs].

Tree processor

Traverse and transform the parsed AST.

Postprocessor

Modify the converted output string.

Build docs developers (and LLMs) love