Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Template System

Attention 💥

Support since v0.2.14.

The template system allows you to create custom embed applications that aren’t supported by the built-in preprocessor. This powerful feature enables you to extend mdbook-embedify with your own interactive components and dynamic content.

Real world example 💡

You can take a looke of my notes book.

It supports caption and preview with much more complex behavior.

Dynamic vs Static Content

Custom templates act like dynamic reusable components. If you want to just copy static content, you should use the include app instead.

Creating a Custom App

Template Folder Setup

Templates must be placed in the assets/templates folder (relative to your book.toml file).

Configure the template folder (optional):

[preprocessor.embedify]
custom-templates-folder = "assets/templates"

📍 Path Requirements:

The template folder path must be relative to the book root directory (where book.toml is located).

Template File Naming

The template filename becomes the app name:

Template FileApp NameUsage
canvas.htmlcanvas{% embed canvas %}
my-widget.htmlmy-widget{% embed my-widget %}
youtube.htmlyoutube{% embed youtube %} (overrides built-in)

⚠️ Override Behavior:

If your custom app name matches a built-in app, the custom template will override the built-in functionality.

Example: Creating a Canvas App

Let’s create a canvas app - a simple drawable canvas component.

Step 1: Create the template file (assets/templates/canvas.html)

Basic HTML structure with styling:

<div class="canvas-container">
  <canvas height="400"></canvas>
</div>
<style>
  .canvas-container {
    width: 100%;
    background: white;
    border-radius: 1rem;
    border: 1px solid #ccc;

    background-size: 20px 20px;
    background-image: linear-gradient(to right, #eee 1px, transparent 1px),
      linear-gradient(to bottom, #eee 1px, transparent 1px);
  }
</style>

Add interactive JavaScript:

<script>
  document.addEventListener("DOMContentLoaded", () => {
    const container = document.querySelector(".canvas-container");
    const canvas = container.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) canvas.width = entry.contentRect.width;
    });
    resizeObserver.observe(container);

    let drawing = false;
    const lastPos = { x: 0, y: 0 };

    // Draw a line from last position to current position
    function draw(x, y) {
      ctx.beginPath();
      ctx.moveTo(lastPos.x, lastPos.y);
      ctx.lineTo(x, y);
      ctx.stroke();
      lastPos.x = x;
      lastPos.y = y;
    }

    // Mouse events
    canvas.addEventListener("mousedown", (e) => {
      drawing = true;
      lastPos.x = e.offsetX;
      lastPos.y = e.offsetY;
    });

    canvas.addEventListener("mousemove", (e) => {
      if (!drawing) return;
      draw(e.offsetX, e.offsetY);
    });

    canvas.addEventListener("mouseup", () => (drawing = false));
    canvas.addEventListener("mouseout", () => (drawing = false));

    // Touch events
    canvas.addEventListener("touchstart", (e) => {
      if (e.touches.length !== 1) return;
      e.preventDefault();
      drawing = true;

      const rect = canvas.getBoundingClientRect();
      lastPos.x = e.touches[0].clientX - rect.left;
      lastPos.y = e.touches[0].clientY - rect.top;
    });

    canvas.addEventListener("touchmove", (e) => {
      if (!drawing || e.touches.length !== 1) return;
      e.preventDefault();
      const rect = canvas.getBoundingClientRect();
      const x = e.touches[0].clientX - rect.left;
      const y = e.touches[0].clientY - rect.top;
      draw(x, y);
    });

    canvas.addEventListener("touchend", () => (drawing = false));
    canvas.addEventListener("touchcancel", () => (drawing = false));
  });
</script>

CSS & JavaScript Integration 💡

You can include CSS and JavaScript directly in template files using <style> and <script> blocks.

Step 2: Add dynamic height support

Make the canvas height configurable using placeholder syntax:

<canvas height="{% height=400 %}"></canvas>

This allows users to customize the height: {% embed canvas height=600 %}

Placeholder Syntax

The template system supports two types of dynamic content:

Syntax Types

TypeSyntaxPurposeExample
Placeholder{% key=default %}Variable substitution{% height=400 %}
Processor{% processor(key=default) %}Content transformation{% markdown(content) %}

Placeholder Examples

SyntaxBehaviorUse Case
{% height %}Required - User must provide valueMandatory configuration
{% height=400 %}Optional - Uses default if not providedOptional configuration
{% markdown(message) %}Processed - Content transformed by processorDynamic content rendering

Available Processors

ProcessorPurposeInputOutput
markdownRenders markdown to HTMLMarkdown textHTML content

Example usage:

<div class="content">{% markdown(description="Description in **markdown**") %}</div>

Complete Canvas Template

Here’s the complete template file for our canvas app:

<div class="canvas-container">
  <canvas height="{% height=400 %}"></canvas>
</div>
<style>
  .canvas-container {
    width: 100%;
    background: white;
    border-radius: 1rem;
    border: 1px solid #ccc;

    background-size: 20px 20px;
    background-image: linear-gradient(to right, #eee 1px, transparent 1px),
      linear-gradient(to bottom, #eee 1px, transparent 1px);
  }
</style>
<script>
  document.addEventListener("DOMContentLoaded", () => {
    const container = document.querySelector(".canvas-container");
    const canvas = container.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) canvas.width = entry.contentRect.width;
    });
    resizeObserver.observe(container);

    let drawing = false;
    const lastPos = { x: 0, y: 0 };

    // Draw a line from last position to current position
    function draw(x, y) {
      ctx.beginPath();
      ctx.moveTo(lastPos.x, lastPos.y);
      ctx.lineTo(x, y);
      ctx.stroke();
      lastPos.x = x;
      lastPos.y = y;
    }

    // Mouse events
    canvas.addEventListener("mousedown", (e) => {
      drawing = true;
      lastPos.x = e.offsetX;
      lastPos.y = e.offsetY;
    });

    canvas.addEventListener("mousemove", (e) => {
      if (!drawing) return;
      draw(e.offsetX, e.offsetY);
    });

    canvas.addEventListener("mouseup", () => (drawing = false));
    canvas.addEventListener("mouseout", () => (drawing = false));

    // Touch events
    canvas.addEventListener("touchstart", (e) => {
      if (e.touches.length !== 1) return;
      e.preventDefault();
      drawing = true;

      const rect = canvas.getBoundingClientRect();
      lastPos.x = e.touches[0].clientX - rect.left;
      lastPos.y = e.touches[0].clientY - rect.top;
    });

    canvas.addEventListener("touchmove", (e) => {
      if (!drawing || e.touches.length !== 1) return;
      e.preventDefault();
      const rect = canvas.getBoundingClientRect();
      const x = e.touches[0].clientX - rect.left;
      const y = e.touches[0].clientY - rect.top;
      draw(x, y);
    });

    canvas.addEventListener("touchend", () => (drawing = false));
    canvas.addEventListener("touchcancel", () => (drawing = false));
  });
</script>

Using Your Custom App

Basic Usage

After creating the template file, use your app in your book:

{% embed canvas height=400 %}

With Default Values

Since height has a default value of 400, you can omit it:

{% embed canvas %}

Interactive Example

Test the canvas app by drawing on it:

Contributing Templates

Want to share your custom templates with the community?

For Personal Use

  • Keep templates in your assets/templates/ folder
  • Customize as needed for your specific use case

For Community Contribution

Copyright © 2025 • Created with ❤️ by MR-Addict