<!--
  canonical-url: https://www.avonture.be/blog/docusaurus-ascii-art
  published:     2025-11-23
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Inject ASCII Art in any HTML pages rendered by Docusaurus

> Inject an ASCII-art banner into every generated Docusaurus HTML page using a lightweight postBuild plugin. Appears in page source or can be rendered visibly.

---
![Inject ASCII Art in any HTML pages rendered by Docusaurus](/img/v2/ascii_art_html.webp)

**TL;DR**

This article demonstrates how to inject a custom ASCII art banner into every HTML page generated by Docusaurus. It involves creating a lightweight `postBuild` plugin that scans generated files and inserts the ASCII art as an HTML comment after the `<!doctype html>` tag. The guide provides steps for creating the ASCII art file, the plugin, and configuring Docusaurus to activate it.

It might be entirely useless, and only the most dedicated tech-heads will spot it, but we're going to dive into how to inject a custom ASCII art banner at the very top of every HTML page generated for our blog.

Right now, just press `CTRL`+`U` (it's the same as `View page source`) to see what we'll do in this article.

## The Result

Once the plugin is in place (covered below), pressing `CTRL`+`U` on any page of the generated site shows this, right after the `<!doctype html>` tag:

![HTML meerkat](./images/html_meerkat.webp)

> **Your website is demonstrably world-class and utterly unique in its field. ;-):**

## Why It Works

* The plugin hooks into Docusaurus's `postBuild` event — it only runs once `yarn docusaurus build` has finished writing HTML to disk.
* It scans every generated `.html` file in the build output.
* For each one, it injects the ASCII art as an HTML comment right after the `<!doctype html>` opening tag — invisible on the rendered page, visible only in the page source.

*Unlike the remark plugins I've written before (see [Creating a search\&replace plugin for Docusaurus](/blog/docusaurus-plugin-replace)), this one doesn't touch the Markdown at all: it works on the generated HTML.*

## Installation

### Create your personalized logo

First, we have to get some ASCII art. If you don't have one yet, you can use this online tool: [Image to ASCII Art Converter](https://folge.me/tools/image-to-ascii). Just upload a small character on it and convert it. *I've also covered the command-line way of producing such banners in [Bash - ASCII art](/blog/bash-ascii-art).*

In your Docusaurus site, please create the `src/data/banner.txt` file and paste your ASCII art in it. Here is mine:

**src/data/banner.txt**

```txt


                                                            :+***###%%%%%%%%##****=:
                                                     .-*##%%%%%%##%%%%%%%%%%%%%%%%%%%%##+.
                                                 .=#%%%#%#%+.:..........::--------=#%%%%%%%%*:
                                              .+#%%%##:........:------------------------+%%@@@@@:.
                                          ..=%#%##=......:----------------------------------*%%@@@#..
                                        .:#%##*=.....:----:---------------------------=#@@%+=--*%@@@%-.
                                       :####+.....:-+%@@@*=:-------------------------=@@@@@@@+----#@@@%=.
                                     :#%%#+....:::*@@@@@@@@:-------------------------=**+-+##=------#@@@%-
                                   .*#%%+....:::::=#%*--++-------------------------------------------=#@@@#:
                                  -#%%*....:::::::::------------------------------------=++++++=-----===%@@#-
                                 =#%%=...:::::::--=++++++----------------------------+************+=--===*@@%=
                                +%%%-...:-:::-++************+-----------------------******************====*@@@=.
                               +%%%-..:-----******************+--------------------****#%%%%%%##*******+===*@@@=
                              =%%%-..:----**********%@@@@@@****+------------------***%@@@%**%@@@@@*******===*@@@=
                             -#%%-..:----*******#@@@@%*=-=%@@@%**-----------------*#@#-::....::-#%@@#*****===*@@@%%=.
                         ...=#%%=...:--=******#%@@+-::.....::-%@%*----------------*%-::+@@#-.....:%@@*****+==+#@@@%%%@:
                       .-%%%%%%%=..:---******#@@*-:....+%@@@%-:-%#----------------#==#:..+@@@%-....#@%*****+==*@@@##%%%*.
                     .-%%%%+*%%=..:---*******%@*:.......*@@@@@%--%*---------------==@@#-+%@@@@@-....@@*****+===#@@%#*%%%%.
                    .%%%#=.=%%%=..:--=******#@@*....**==%@@@@@@%:+*----------------*@@@@@@@@@@@-....@%******+==#@@@##+%@%+
                   .%%%#..-#@@@=..:--+******#%@*...:*@@@@@@@@@@%==#-------++++++=--=@@@@@@@@@@@-...=@%******+==+%@@##*%@@%
                   *%%#.:+*%@@#-.:---+*******#@*:...+@@@@@@@@@@%-#=---=*#*+=====*%*=-%@@@@@@@%-...=@%******+====#@@%##%@@%
                   %%%+.++#%@@*-::----********#@#-...-#@@@@@@%=:*+:::=#%#*+++++*#%%%-:=+*#+=-...+%@#******+-:===#@@@#*@@@%
                   %%%+=+##%@@#-------=*********%%*-...:------**-:...=@@%%%%%%@@@@@@#..:=++**#%##********+:..-==#@@@#%@@@:
                   %%%%++**#@@#=------.:**********#%%#####%*#*-:.....+@@@@@@@@@@@@@%-:....:=**********+:.....-==#@@%%%@%:
                   -@@@#+**#@@@+-----:...:*****************-:.........:*@@@@@@@@@@#=-:::....................:-=*%@@@@@%.
                    =@@@%+*#@@@+-----:.......-*+++****=:.................-#@@@@*=--::::=*...................:-=#@@@@+.
                     .%@@@#*%@@%=-----........................:@@#............:-::-:-=%@*:::...............:==*@@@-
                      .-%@@@@@@@*-----.........................:*%@#-:......:::::-=+@@#=::::::............:==+#@@*:
                         .=##%@@@+-----...........................-#%@@@%*===+#@@@%#*::::::::::.........:-==+%@@#:
                              +%@@*-----:.............................:+#******#-:::::::::::::::.......-===*@@@*:
                               =%@@#=-----:...................................::::::::::::::::::....:-====#@@%=.
                                -#@@@*------:................................:::::::::::::::::::::-====+#@@@#:
                                 .-%@@@#-------::......................:::::::::::::::::::::::--=====#%@@@#-        =+*+.
                                   .-%@@@%#==-------::.........::::::::::::::::::::::::::---=====-#%@@@@#:        +%@@%@@*
                                     .:%@@@@%#+========------::::::::::::::::::::------======-*%@@@@@#:.         .#@=--*@%-
                                        .:*@@@@@@%#+--==--------------------------------====#@@@@@+:.            :#@=---@@=
                                           ..:#@@@@@@@@%=------------------------------=-===#@@@:                :#@---=@@=
                                                  .+%@@@=-----------------------------------*%@@:                .#@%*-@@@@@@%+.
                                                    -#@@=------------------------------------#@@+.             .*%%%*%@@=-=%=%@#-
                                                    .*%%-..--------------------:::::::-------#@@@:            -#@=----=@%=*%=+#@*:
                                                     *%%#..----------------:..............:--+%@@:.           +@@+==#%@@*#@#+*%@*:
                                                     +%%%:..-------------:..................:-#@@@%==:    ..-+%%%*+++###%@@@%@@%-
                                                     :#%%:..:----------:......................*@@@@@@%%%%%%%%%%*::=++++++*#@@%+.
                                                  .=#%%%*-...:--------:........................#@@#+++*###*==:..:---===+++*@@#.
                                                -#%%%#=...::---------:.........................=%@@*--::::::::---------==+@@%+
                                             .-#%%%+:..::------------:..........................#@@@=-----------------===@@@*.
                                            :#%%#=...::--------------:..........................-%@@%=-------------====#@@@*.
                                          .#%%%+...:-----------------:..........................:+@@@+==---------====+%%@%:
                                         .%%%*...:::-----------------:...........................-%@@%====--=======+%@@@%
                                        .%%%*..:--------------=%=----:...........................--@@@#++++=====+#@@@@#.
                                        %%%#..::------------+%%+-----:...........................:-%@@%+++++**%@@@@#+.
                                       .%%#:..::----------#@@%*=-----:............................-=@@@@@@@@@@@%#+.
                                       =%%#..-:----------%@@@=+=-----:............................-=#@@@@%#*+=.
                                       :%%%-------------=+%@@#=------:............................-==@@%+
                                        %%%#-------------=+%@@#------:............................:==@@@#
                                        .@@%#=------------=+@@%+-----:.............................==*@@#:
                                         .#@@%*=---------==++@@%-----:.............................===@@@*
                                          .-%@@@%%%%%%%*++++*@@%-----:.............................===%@@*
                                            ..=@@@@@*+++++++@@%=-----:.............................-==+@@#-
                                               =@@#+++++++%@@@+=----:..............................:==+@@@*
                                               =@@#*++%@@@@@%===----:..............................:===%@@*
                                               .=%@@@@@@%%++===-----:..............................:===*@@#:
         .:=+%%@#*++--------:::..........:::-----+%@@#+++++===-----:...............................:===+@@@+
       :+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@#+=====--------:..............................::===+@@@*
      :#%%%******++++++**#######*++++=++++++++*#%%%=.:-------------:............................::::===+%@@*
      -%%%#*********************-:::::::::::::=#%%+..:-------------:..........................::::::===+#@@*.
      .+%%%#*******************=--------------*%%*:.::-------------:........................::::::::====+@@#:
       .=%@@@%#****************=-------------+@@#=..:--------------:......................:::::::::-====+@@#-
         .-#@@@%%*********####*=============+@@@+..:---------------:....................::::::::::-=====+@@@+
           ..+%@@@@@@#***#####+=============#@@*..:-----------------:................::::::::::::-======+@@@+
                .#@@@@@@@@%###+============+@@@*.:-------------------:............::::::::::::::========+@@@+
                    .*%%@@@@@@@@@@%*=++++++#@@*..:---------------------:.......::::::::::::::--=====+===+@@@+
                         .:*##%@@@@@@@@@@@@@@@*.:-------------------------::::::::::::::::--============+@@@+
                                  .:=+***##@@@*.:-----------------------======---------===========+=+===+@@@+
                                          :@@%+:--------------------================================+===+@@@+
                                          :@@%=------------------====================++++++++++++++=====+@@@+
                                         .:@@#=---------------==========*###%@@@@@@@@@@@@%%##*+==+++====+@@%=
                                         .-@@%=------------=========*#%@@@@@@@@##@@@@@@@@@@%##*+++++++++*@@#-
                                          :@@%+----------========+#%@@@#=-...    :@@@%++++++++++++++++++*@@#:
                                       =#%%@@%*-------=========+#@@@*-.          .-@@@#=++++++++++++++++*@@@@%%*:
                                     =%%@@#:::------==========+%@@#:.             .=@@%*+++++++++++++++++=-=*%@@@%-
                                    #@@@....-----============+@@@#                 .*@@#++++++++++++++++++++=--*@@@:
                                  .:%@@@@@@@@@@@@@@@@@@@@@@@@@@@#                   :%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#:
                                  .+############################:                    :##############################:.
```

### Create the plugin and load it

Now, let's create the plugin. Please create `plugins/ascii-injector/index.mjs` and copy/paste the source code here below to your file:

**plugins/ascii-injector/index.mjs**

```mjs
/**
 * Docusaurus Plugin: ascii-injector
 *
 * Purpose:
 * This plugin will prepend an ASCII art banner (from a specified text file)
 * in all generated HTML files as an HTML comment.
 *
 * Usage:
 * - Save this plugin as `plugins/ascii-injector/index.mjs`
 * - Create the file containing the ASCII art in `src/data/banner.txt`.
 * - Add the plugin to `docusaurus.config.js` plugins array.
 *
 * Please see readme.md for more details
 */

import { promises as fs } from "node:fs";
import path from "node:path";

/**
 * Recursively collect all HTML files in a given directory.
 */
async function collectHtmlFiles(dir) {
  const entries = await fs.readdir(dir, { withFileTypes: true });
  const files = await Promise.all(
    entries.map(async (entry) => {
      const fullPath = path.resolve(dir, entry.name);
      if (entry.isDirectory()) return collectHtmlFiles(fullPath);
      if (entry.isFile() && entry.name.endsWith(".html")) return [fullPath];
      return [];
    }),
  );
  return files.flat();
}

/**
 * Docusaurus plugin that injects an ASCII art banner into all generated HTML files.
 *
 * @param {object} context - Docusaurus plugin context (unused).
 * @param {object} options - Plugin configuration options.
 * @param {string} options.bannerPath - Relative path (from siteDir) to the ASCII banner file.
 */
export default function asciiInjectorPlugin(context, options) {
  if (!options || typeof options.bannerPath !== "string") {
    throw new Error("[ascii-injector] Missing required option: bannerPath (string)");
  }

  return {
    name: "ascii-injector-plugin",

    async postBuild({ outDir, siteDir }) {
      const bannerPath = path.resolve(siteDir, options.bannerPath);

      let rawBanner;
      try {
        rawBanner = await fs.readFile(bannerPath, "utf8");
      } catch (err) {
        console.error(`[ascii-injector] Banner file missing: ${err.message}`);
        return;
      }

      const commentBanner = `<!--\n${rawBanner}\n-->\n\n`;

      const htmlFiles = await collectHtmlFiles(outDir);
      console.log(
        `[ascii-injector] Preparing to inject banner into ${htmlFiles.length} HTML files...`,
      );

      const results = await Promise.allSettled(
        htmlFiles.map(async (filePath) => {
          try {
            const html = await fs.readFile(filePath, "utf8");

            // proceed to inject; files are always regenerated during build

            let updated;

            // Default: insert comment after <!doctype ...>
            const doctype = /<!doctype[^>]*>/i;
            if (doctype.test(html)) {
              updated = html.replace(doctype, (m) => `${m}\n${commentBanner}`);
            } else {
              // fallback to prepending comment
              updated = commentBanner + html;
            }

            // Write atomically: temp file + rename avoids race conditions
            // with other postBuild plugins that read HTML concurrently.
            const tmpPath = `${filePath}.tmp`;
            await fs.writeFile(tmpPath, updated, "utf8");
            await fs.rename(tmpPath, filePath);
            return { status: "ok", filePath };
          } catch (err) {
            return { status: "error", filePath, error: err };
          }
        }),
      );

      const ok = results.filter(
        (r) => r.status === "fulfilled" && r.value && r.value.status === "ok",
      ).length;
      const skipped = results.filter(
        (r) => r.status === "fulfilled" && r.value && r.value.status === "skipped",
      ).length;
      const failed =
        results.filter(
          (r) => r.status === "fulfilled" && r.value && r.value.status === "error",
        ).length + results.filter((r) => r.status === "rejected").length;

      console.log(
        `[ascii-injector] Injection complete — ok: ${ok}, skipped: ${skipped}, failed: ${failed}`,
      );
    },
  };
}
```

Almost done. Now, finalize the installation by editing your `docusaurus.config.js` file and, under `plugins`, please do this:

**docusaurus.config.js**

```javascript
// [...]
plugins: [
    // [...]
    [
        "./plugins/ascii-injector/index.mjs",
        { bannerPath: "src/data/banner.txt" },
    ],
],
```

The very last thing to do is to restart your Docusaurus website to load the updated configuration file.

## More Demos

### Time to test

Nothing new here, just generate the static version of your site.

```bash
$ yarn docusaurus build
$ yarn docusaurus serve
```

Once your website is generated, just open any page and press `CTRL`+`U` and tadaaa, your ASCII art is there — exactly the result shown at the top of this article.

## Under the Hood (skip this if you just want to use it)

> **Not during preview mode:**
>
> This plugin is only fired during the `postBuild` event i.e. only after your blog has been rendered as an HTML page. So if you're running Docusaurus in preview mode; you'll not see the ASCII art.

## Conclusion

A `postBuild` plugin, one ASCII art file, and a three-line config addition — and every page your site generates carries a small, pointless, entirely intentional signature in its source. If the command-line side of generating ASCII banners interests you more than the Docusaurus plumbing, [Bash - ASCII art](/blog/bash-ascii-art) covers that half.
