Renderer Plugins

Renderer Plugins

Renderer plugins form the heart of notion-to-md v4’s transformation system. They determine how Notion blocks are converted into your desired output format, whether that’s Markdown, HTML, JSX, or any other text-based format.

This guide is a must read for anyone looking to customize existing plugins or create new ones from scratch.

Core Components

Every renderer plugin consists of four essential components:

graph TD
    A[Template] --> E[Renderer Plugin]
    B[Variables] --> E
    C[Block Transformers] --> E
    D[Annotation Transformers] --> E
    E --> F[Final Output]

    style A fill:#e6fffa,stroke:#319795,color:#000000
    style B fill:#ebf8ff,stroke:#3182ce,color:#000000
    style C fill:#fefcbf,stroke:#d69e2e,color:#000000
    style D fill:#fed7d7,stroke:#e53e3e,color:#000000
    style E fill:#f0f4f8,stroke:#4a5568,color:#000000

Renderer Lifecycle and Flow

The rendering process follows a specific flow:

flowchart TD
    A[Receive Block Data] --> B[Process Blocks]
    B --> C{Process Each Block}
    C --> D[Apply Block Transformer]
    D --> E[Process Rich Text]
    E --> F[Apply Annotation Transformers]
    F --> G[Collect Transformed Content]
    G --> H[Resolve Variables]
    H --> I[Apply Template]
    I --> J[Return Final Output]

    style A fill:#e6fffa,stroke:#319795,color:#000000
    style J fill:#e6fffa,stroke:#319795,color:#000000
    style D fill:#fefcbf,stroke:#d69e2e,color:#000000
    style F fill:#fed7d7,stroke:#e53e3e,color:#000000
    style H fill:#ebf8ff,stroke:#3182ce,color:#000000
    style I fill:#ebf8ff,stroke:#3182ce,color:#000000
  1. Initialization: The renderer receives block data from the processor chain
  2. Block Processing: Each block is processed according to its type
    • The appropriate block transformer is applied
    • Rich text within the block is processed
    • Annotation transformers are applied to format text
  3. Content Collection: Transformed blocks are collected
  4. Variable Resolution: Template variables are resolved
  5. Template Application: The template is populated with variable content
  6. Output: The final formatted content is returned

Core Architecture

Renderer plugins are built on the BaseRendererPlugin abstract class, which implements the ProcessorChainNode interface:

classDiagram
    class ProcessorChainNode {
        <>
        +process(data: ChainData): Promise~ChainData~
    }

    class BaseRendererPlugin {
        #template: string
        #variables: Record~string, VariableResolver~
        #blockTransformers: Record~string, BlockTransformer~
        #annotationTransformers: Record~string, AnnotationTransformer~
        +process(data: ChainData): Promise~ChainData~
        #processBlock(block: Block): Promise~string~
        #processRichText(richText: RichText[]): Promise~string~
    }

    class MDXRenderer {
        #template: string
        #variables: Record~string, VariableResolver~
        #blockTransformers: Record~string, BlockTransformer~
        #annotationTransformers: Record~string, AnnotationTransformer~
    }

    class HTMLRenderer {
        #template: string
        #variables: Record~string, VariableResolver~
        #blockTransformers: Record~string, BlockTransformer~
        #annotationTransformers: Record~string, AnnotationTransformer~
    }

    ProcessorChainNode <|.. BaseRendererPlugin : implements
    BaseRendererPlugin <|-- MDXRenderer : extends
    BaseRendererPlugin <|-- HTMLRenderer : extends

This architecture provides a consistent interface while allowing for complete customization of the rendering process.

Data Context

Throughout the rendering process, components have access to a comprehensive context object:

interface RendererContext {
  pageId: string;                // The Notion page ID
  pageProperties: PageProperties; // Notion page properties
  metadata: ContextMetadata;     // Additional metadata
  block: Block;                  // Current block (during transforms)
  blockTree: Block[];            // All blocks
  variableData: VariableCollector; // Collected variable data
  transformers: {
    blocks: Record<BlockType, BlockTransformer>;
    annotations: Record<AnnotationType, AnnotationTransformer>;
  };
  utils: {
    processRichText: (richText: RichText[]) => Promise<string>;
    processBlock: (block: Block) => Promise<string>;
  };
}

This context provides access to everything needed for sophisticated rendering decisions, from page metadata to utility functions.

Default Renderer

notion-to-md v4 comes with a default MD/MDX renderer, which serves as both a useful default and an example implementation:

ℹ️
For detailed view at the implementation, please refer the default renderer notion-to-md ships with, as it’s a good reference for creating your own renderer.
// Default MDX renderer (simplified)
class MDXRenderer extends BaseRendererPlugin {
  protected template = `{{{frontmatter}}}{{{imports}}}{{{content}}}`;

  // Default variables for frontmatter, imports, and content
  protected variables = { /* ... */ };

  // Transformers for Markdown formatting
  protected blockTransformers = { /* ... */ };
  protected annotationTransformers = { /* ... */ };
}

Using a renderer is straightforward:

// Using the default renderer
const n2m = new NotionConverter(notionClient);

// Using a custom renderer
const htmlRenderer = new HTMLRenderer();
const n2m = new NotionConverter(notionClient)
  .withRenderer(htmlRenderer);