rehype-image-toolkit
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 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.
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, and unwrapping media from paragraph.
When should I use this?
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. - 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.
Installation
This package is suitable for ESM only. In Node.js (version 16+), install with npm:
npm install rehype-image-toolkit
or
yarn add rehype-image-toolkit
Usage with markdown source
Say 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>
Usage with html source
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>
Features
Convert image syntax to videos/audio
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.
Use the title
attribute to customize images, videos, and audio
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 height
If 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.
Create an autolink for images (Explicit Autolink)
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.png
in 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 caption for images/videos/audio (Explicit Figure)
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.
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.

Add caption for images/videos/audio (Implicit Figure)
There is an option implicitFigure
for adding an image/video/audio into <figure>
and adding a caption. 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.
Unwrap images from paragraph
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.


Keep videos/audio inline in paragraph
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 !
Summary table of directives
to be created (a PR is welcome).
Options
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);
explicitAutolink
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">
.
explicitFigure
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">
.
implicitFigure
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}`

figureCaptionPosition
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.
addControlsForVideos
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>
addControlsForAudio
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>
enableMdxJsx
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
enableMdxJsx
to{enableMdxJsx: false}
. This prevents the plugin from searching forMdxJsx
elements, 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>
Examples:
Syntax tree
This plugin modifies the hast
(HTML abstract syntax tree).
Types
This package is fully typed with TypeScript. The plugin exports the type ImageToolkitOptions
.
Compatibility
This plugin works with rehype-parse
version 1+, rehype-stringify
version 1+, rehype
version 1+, and unified version 4+
.
Security
Use of rehype-image-toolkit
involves rehype (hast), but doesn't lead to cross-site scripting (XSS) attacks.
My Plugins
I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to have a look my plugins.
My Remark 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 addins
element in markdownremark-flexible-paragraphs
– Remark plugin to add custom paragraphs with customizable properties in markdownremark-flexible-markers
– Remark plugin to add custommark
element with customizable properties in markdownremark-flexible-toc
– Remark plugin to expose the table of contents viavfile.data
or via an option referenceremark-mdx-remove-esm
– Remark plugin to remove import and/or export statements (mdxjsEsm)
My Rehype Plugins
rehype-pre-language
– Rehype plugin to add language information as a property topre
elementrehype-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.meta
tocode.properties.metastring
rehype-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.
My Recma Plugins
recma-mdx-escape-missing-components
– Recma plugin to set the default value() => null
for 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 theprops
parameter into the_props
in thefunction _createMdxContent(props) {/* */}
in the compiled source in order to be able to use{props.foo}
like expressions. It is useful for thenext-mdx-remote
ornext-mdx-remote-client
users innextjs
applications.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 gettingReact
instance 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
, andtitle
attributes of markdown link and image syntax in MDX.
License
MIT License © ipikuka