If you find rehype-image-toolkit useful in your projects, consider supporting my work.
Your sponsorship means a lot 💖
Be the first sponsor and get featured here and on my sponsor wall.
Thank you for supporting open source! 🙌
This package is a unified (rehype) plugin that enhances Markdown image syntax ![]() and Markdown/MDX media elements (<img>, <audio>, <video>) by;
- auto-linking bracketed or parenthesized image URLs,
- wrapping them in
<figure>with optional caption<figcaption>, - unwrapping images/videos/audio from paragraph,
- parsing directives in title for styling and adding attributes,
- dynamically converting images into
<video>or<audio>elements based on file extension.
unified is a project that transforms content with abstract syntax trees (ASTs) using the new parser micromark. remark adds support for markdown to unified. mdast is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree. rehype is a tool that transforms HTML with plugins. hast stands for HTML Abstract Syntax Tree (HAST) that rehype uses.
Markdown natively supports images but lacks built-in syntax for videos and audio. rehype-image-toolkit extends image syntax, automatically transforming it into <video> or <audio> elements based on file extensions, supporting additional attributes, providing custom directives for autolink to originals, wrapping media with <figure> and adding caption <figcaption>, and unwrapping media from paragraph.
From what I’ve seen, most Remark and Rehype plugins that handle Markdown images apply their features globally, without offering much flexibility. In some cases, I need more control—certain images should be excluded from these transformations. For example, I might NOT want every image to be wrapped in a <figure>, include a caption, be automatically linked to its source, or unwrapping from paragraph.
That's why each feature in rehype-image-toolkit is individually controllable via directives. Its most distinct advantage over other remark/rehype plugins.
I designed rehype-image-toolkit as an all-in-one solution, bringing together all essential image-related features for markdown and MDX in a single toolkit.
rehype-image-toolkit is ideal for:
- adding videos/audio using Markdown image syntax – No need for HTML or custom MDX components.
- styling and adding attributes to images/videos/audio – Easily add classes, IDs, styles, and other attributes.
- adding
<figure>and caption – Easily wrap in a<figure>element with an optional caption<figcaption>. - unwrapping media from paragraph – Control which images/videos/audio to be or NOT to be extracted from paragraph.
- adding autolink to the original image - Control which images to be automatically linked to their original source.
This package is suitable for ESM only. In Node.js (version 16+), install with npm:
npm install rehype-image-toolkitor
yarn add rehype-image-toolkitSay we have the following markdown file, example.md:
(pay attention to directives)
It ensures adding videos/audio using image syntax. 
It adds autolink to original. 
It adds figure and caption. 
It unwraps images from paragraph 
It adds attributes. Our module, example.js, looks as follows:
import { read } from "to-vfile";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeImageToolkit from "rehype-image-toolkit";
import rehypeStringify from "rehype-stringify";
main();
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeImageToolkit)
.use(rehypeStringify)
.process(await read("example.md"));
console.log(String(file));
}Now, running node example.js you will see.
<p>It ensures adding videos/audio using image syntax.</p>
<video>
<source src="video.mp4" type="video/mp4" />
</video>
<p>
It adds autolink to original.
<a href="https://example.com/image.png" target="_blank">
<img src="https://example.com/image.png" alt="alt"/>
</a>
</p>
<p>It adds figure and caption.</p>
<figure>
<img src="image.png" alt="Image Caption" />
<figcaption>Image Caption</figcaption>
</figure>
<p>It unwraps images from paragraph</p>
<img src="image.png" alt="alt" />
<p>It adds attributes.</p>
<video title="title" width="640" height="480" autoplay>
<source src="video.mp4" type="video/mp4" />
</video>Without rehype-image-toolkit the output would be:
<p>It ensures adding videos/audio using image syntax. <img src="video.mp4" alt=""></p>
<p>It adds autolink to original. <img src="%5Bhttps://example.com/image.png%5D" alt="alt"></p>
<p>It adds figure and caption. <img src="image.png" alt="^Image Caption"></p>
<p>It unwraps images from paragraph <img src="image.png" alt="&alt"></p>
<p>It adds attributes. <img src="video.mp4" alt="" title="title > 640x480 autoplay"></p>rehype-image-toolkit also works with references in markdown.
![cat image][reference-image] meows ![~][reference-audio]
[reference-image]: [image.png] "cat image"
[reference-audio]: audio.mp3 "> autoplay"will produce (pay attention to brackets around the src of the image; and title directive and tilda ~ inline directive for the audio):
<p>
<a href="image.png" target="_blank">
<img src="image.png" alt="cat image" />
</a> meows
<audio autoplay>
<source src="audio.mp3" type="audio/mpeg" />
</audio>
</p>Actually, you don't need to use rehype-image-toolkit for html sources since you can write direct html structure for adding figure and caption, adding attributes and wrapping assets with an anchor link. But anyway, I've wanted to support that features for html sources as well.
Say example.html looks as follows:
(pay attention to directives)
<p>
It adds autolink to original.
<img src="[https://example.com/image.png]" alt="alt"/>
</p>
<p>
It adds figure and caption.
<img src="image.png" alt="^Image Caption"/>
</p>
<p>
It adds attributes.
<img src="image.png" title="title > 60x60"/>
</p>
<p>
It unwraps videos/audio from paragraph by default.
<video src="video.mp4"></video>
</p>
<p>
It keeps videos/audio in paragraph via tilda directive.
<video src="video.mp4" alt="~"></video>
</p>Our module, example.js, looks as follows:
import { read } from "to-vfile";
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeImageToolkit from "rehype-image-toolkit";
import rehypeStringify from "rehype-stringify";
main();
async function main() {
const file = await unified()
.use(rehypeParse, { fragment: true })
.use(rehypeImageToolkit)
.use(rehypeStringify)
.process(await read("example.md"));
console.log(String(file));
}Now, running node example.js you will see.
<p>
It adds autolink to original.
<a href="https://example.com/image.png" target="_blank">
<img src="https://example.com/image.png" alt="alt">
</a>
</p>
<p>
It adds figure and caption.
</p>
<figure>
<img src="image.png" alt="Image Caption">
<figcaption>Image Caption</figcaption>
</figure>
<p>
It adds attributes.
<img src="image.png" title="title" width="60" height="60">
</p>
<p>
It unwraps videos/audio from paragraph by default.
</p>
<video src="video.mp4"></video>
<p>
It keeps videos/audio in paragraph via tilda directive.
<video src="video.mp4"></video>
</p>Markdown lacks built-in support for video and audio, only providing image syntax. rehype-image-toolkit repurposes the image syntax to render video/audio elements based on file extensions.
is transformed into<video>element
<video>
<source src="example.mp4" type="video/mp4">
</video>is transformed into<audio>element
<audio>
<source src="example.mp3" type="audio/mpeg">
</audio>Since <video> and <audio> are block-level elements by default, rehype-image-toolkit unwraps them from paragraphs, splitting the text or other content around their original positions. If you don't want a video/audio to be extracted from paragraph use tilda ~ in the begining of alt text . It may be useful if you want an audio to be an inline element within paragraph in support of CSS.
As you know, Markdown’s image syntax supports an optional title attribute: 
rehype-image-toolkit extends this by recognizing custom directives after greater operator (>) in title.
width and hight attributes must be in pixel unit
You can use px or just number for example 320 or 320px. The CSS units other than pixel will go to the style attribute.


 --> this will set the style attribute rather than width and heightIf no title is needed, start with just greater operator (>).
Directives must be separated by a single space, with no spaces within attribute values and no quotes.

There is a simple syntax for width and hight, using lowercase "x" character in the middle.
In this syntax, the pixel units should be a number, do not use px.
// Both dimension

// Both dimension WRONG!
 X WRONG!
// Only width

// Only height

// Both dimension, other than px unit will go to the style attribute
The width and height attributes on images are treated specially. When used without a unit, the unit is assumed to be pixels. However, any of the CSS unit identifiers can be used. There shouldn't be any space between the number and the unit.
Wrap the link of the source in brackets or parentheses. It is valid for only if the image source starts with protokol-like links like http://, web sites start with www., root relative links start with a slash / and if just an image name like image.png.
### pay attention to additional brackets around the link

### pay attention to additional parentheses around the link
 "title")will produce the same output below:
<p>
<a href="http://example.com/image.png">
<img src="http://example.com/image.png" alt="alt" title="title">
</a>
</p>Wrapping the source attribute with brackets or parentheses produces mostly the same behavior/output, but only differs in case the image is wrapped in a <figure> element.
- Wrapping the source attribute with brackets provides autolinking for whole
<figure>element including caption. - Wrapping the source attribute with parentheses provides autolinking for only the
<img>element in the<figure>.

)will produce:
<a href="image.png" target="_blank">
<figure>
<img src="image.png" alt="caption">
<figcaption>caption</figcaption>
</figure>
</a>
<figure>
<a href="image.png" target="_blank">
<img src="image.png" alt="caption">
</a>
<figcaption>caption</figcaption>
</figure>if you want to set different target for the <figure> and for the <img> inside, you can wrap the src attribute with parentheses, not brackets, and use the formal link syntax of markdown together:
[)](https://example.com)will produce:
<a href="https://example.com">
<figure>
<a href="image.png" target="_blank">
<img src="image.png" alt="Caption" />
</a>
<figcaption>Caption</figcaption>
</figure>
</a>According to the HTML specification, anchor elements cannot be nested. Despite being invalid, browsers try to render this gracefully. The actual behavior can differ slightly between browsers, but in most modern browsers like Chrome, Firefox, and Safari:
- The inner
<a href="image.png" target="_blank">wrapping the<img>will take precedence when clicking directly on the image. - Clicking directly on the
<img>will openimage.pngin a new tab (because oftarget="_blank"). - Clicking outside the image but still inside the outer
<a>(e.g., on thefigcaption) will follow the outer link (https://example.com).
Add a caret ^ special directive at the start of the alt attribute in order to wrap the media asset with <figure> element and add a caption <figcaption>.
Since <figure> is block-level element by default, rehype-image-toolkit unwraps it from paragraphs, splitting the text or other content around their original position. There is no choice not to be extracted!
will produce the html output:
<figure>
<img src="image.png" alt="Caption of the image" title="title" />
<figcaption>Caption of the image</figcaption>
</figure>Add a double caret ^^ special directive at the start of the alt attribute in order to only wrap the asset with <figure> element but NOT to include a caption.

will produce the html output (notice there is no caption):
<figure>
<img src="image.png" alt="alt" title="title" />
</figure>
<figure>
<video title="title">
<source src="video.mp4" type="video/mp4">
</video>
</figure>If you want just a regular inline image, do not use any ^ directive in the begining of alt attribute.
There is an option implicitFigure for adding an image/video/audio into <figure> and adding a caption <figcaption>. If you set {implicitFigure: true} in the options, an image will be rendered as a figure without any directive in the content if it is alone in the paragraph . The image’s alt text will be used as the caption. This feature is aligned with pandoc markdown implicit figure.
## Assume you set `{implicitFigure: true}`
If you just want a regular inline image, when you set {implicitFigure: true}, just make sure it is not the only thing in the paragraph or put a tilda ~ or ampersand & directives in the start of alt attribute, since these directives have priority:
## Assume you set `{implicitFigure: true}`
### This image won't be a `<figure>` and stay in the paragraph as inline content

### This image won't be a `<figure>`, but it is going to be unwrapped from paragraph
Just I want to stress again that if you want the image be in a <figure> you can use directive caret ^ in the start of alt for explicit figure, as explained in the previous. No matter what the image is alone or not.
Add a ampersand & special directive at the start of the alt attribute in order to unwrap the image from paragraph. Sometime you may want to unwrap images without adding it in a <figure>. This directive ensures the image is extracted without adding it in a figure.

Add a tilda ~ special directive at the start of the alt attribute in order to keep videos/audio in a paragraph. This is helpful when you want these elements to remain inline in the paragraph. Normally, rehype-image-toolkit unwraps videos/audio from paragraph by default.
Here is the cat voice . So nice !to be created (a PR is welcome).
All options are optional and have default values.
type ImageToolkitOptions = {
explicitAutolink?: boolean; // default is "true"
explicitFigure?: boolean, // default is "true"
implicitFigure?: boolean; // default is "false"
figureCaptionPosition?: "above" | "below"; // default is "below"
addControlsForVideos?: boolean; // default is "false"
addControlsForAudio?: boolean; // default is "false"
enableMdxJsx?: boolean; // default is "true"
};
use(rehypeImageToolkit, ImageToolkitOptions);It is a boolean option which is for enabling or disabling ExplicitAutolink feature.
By default, it is true. See more explanation about ExplicitAutolink here.
use(rehypeImageToolkit, {
explicitAutolink: false,
});This will disable autolinking to original, removing the directive brackets or parentheses in the src attribute.
will produce standard image element without wrapping with an anchor <image src="image.png" alt="alt">.
It is a boolean option which is for enabling or disabling ExplicitFigure feature.
By default, it is true. See more explanation about ExplicitFigure here.
use(rehypeImageToolkit, {
explicitFigure: false,
});This will disable adding <figure> and caption, removing the directive caret ^ in the alt attribute.
will produce standard image element without figure and caption <image src="image.png" alt="caption">.
It is a boolean option which is for enabling or disabling ImplicitFigure feature.
By default, it is false. See more explanation about ImplicitFigure here.
## Assume you set `{implicitFigure: true}`
It is a "above" | "below" union string option which is for placing the caption below or above of the asset.
By default, it is below.
use(rehypeImageToolkit, {
figureCaptionPosition: "above",
});Now, the caption will be the above of the asset.
It is a boolean option which is for adding controls property to video elements by default.
By default, it is false.
use(rehypeImageToolkit, {
addControlsForVideos: true,
});Now, video elements, like , will have controls attribute by default.
<video controls>
<source src="example.mp4" type="video/mp4">
</video>It is a boolean option which is for adding controls property to audio elements by default.
By default, it is false.
use(rehypeImageToolkit, {
addControlsForAudio: true,
});Now, audio elements, like , will have controls attribute by default.
<audio controls>
<source src="example.mp3" type="audio/mpeg">
</audio>It is a boolean option which is for enabling or disabling MdxJsx Elements within MDX.
As you know, the html-like (jsx) syntax in MDX contents are not HTML elements, actually MdxJsx elements. If you don't want the plugin process html-like (jsx) syntax in the MDX document, set the enableMdxJsx to {enableMdxJsx: false}.
Another consideration: if your content is pure Markdown (markdown + HTML syntax) and not MDX, set
enableMdxJsxto{enableMdxJsx: false}. This prevents the plugin from searching forMdxJsxelements, resulting in faster rendering.
By default, it is true.
use(rehypeImageToolkit, {
enableMdxJsx: false,
});This will cause the plugin doesn't touch MdxJsx elements within MDX contents.

<img src="image.png" alt="^caption"/>will produce only first one is processed:
<figure>
<img src="image.png" alt="caption"/>
<figcaption>caption</figcaption>
</figure>
<img src="image.png" alt="^caption"/>if you keep enableMdxJsx is true, the result would be:
<figure>
<img src="image.png" alt="caption"/>
<figcaption>caption</figcaption>
</figure>
<figure>
<img src="image.png" alt="caption"/>
<figcaption>caption</figcaption>
</figure>This plugin modifies the hast (HTML abstract syntax tree).
This package is fully typed with TypeScript. The plugin exports the type ImageToolkitOptions.
This plugin works with rehype-parse version 1+, rehype-stringify version 1+, rehype version 1+, and unified version 4+.
Use of rehype-image-toolkit involves rehype (hast), but doesn't lead to cross-site scripting (XSS) attacks.
I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to have a look my plugins.
remark-flexible-code-titles– Remark plugin to add titles or/and containers for the code blocks with customizable propertiesremark-flexible-containers– Remark plugin to add custom containers with customizable properties in markdownremark-ins– Remark plugin to addinselement in markdownremark-flexible-paragraphs– Remark plugin to add custom paragraphs with customizable properties in markdownremark-flexible-markers– Remark plugin to add custommarkelement with customizable properties in markdownremark-flexible-toc– Remark plugin to expose the table of contents viavfile.dataor via an option referenceremark-mdx-remove-esm– Remark plugin to remove import and/or export statements (mdxjsEsm)
rehype-pre-language– Rehype plugin to add language information as a property topreelementrehype-highlight-code-lines– Rehype plugin to add line numbers to code blocks and allow highlighting of desired code linesrehype-code-meta– Rehype plugin to copycode.data.metatocode.properties.metastringrehype-image-toolkit– Rehype plugin to enhance Markdown image syntax![]()and Markdown/MDX media elements (<img>,<audio>,<video>) by auto-linking bracketed or parenthesized image URLs, wrapping them in<figure>with optional captions, unwrapping images/videos/audio from paragraph, parsing directives in title for styling and adding attributes, and dynamically converting images into<video>or<audio>elements based on file extension.
recma-mdx-escape-missing-components– Recma plugin to set the default value() => nullfor the Components in MDX in case of missing or not provided so as not to throw an errorrecma-mdx-change-props– Recma plugin to change thepropsparameter into the_propsin thefunction _createMdxContent(props) {/* */}in the compiled source in order to be able to use{props.foo}like expressions. It is useful for thenext-mdx-remoteornext-mdx-remote-clientusers innextjsapplications.recma-mdx-change-imports– Recma plugin to convert import declarations for assets and media with relative links into variable declarations with string URLs, enabling direct asset URL resolution in compiled MDX.recma-mdx-import-media– Recma plugin to turn media relative paths into import declarations for both markdown and html syntax in MDX.recma-mdx-import-react– Recma plugin to ensure gettingReactinstance from the arguments and to make the runtime props{React, jsx, jsxs, jsxDev, Fragment}is available in the dynamically imported components in the compiled source of MDX.recma-mdx-html-override– Recma plugin to allow selected raw HTML elements to be overridden via MDX components.recma-mdx-interpolate– Recma plugin to enable interpolation of identifiers wrapped in curly braces within thealt,src,href, andtitleattributes of markdown link and image syntax in MDX.
MIT License © ipikuka