recma-mdx-interpolate
v1.1.6
Published
Recma plugin to enable interpolation of identifiers wrapped in curly braces within the `alt`, `src`, `href`, and `title` attributes of markdown link and image syntax in MDX
Downloads
202
Maintainers
Readme
A robust Next.js newsletter Next.js Weekly is sponsoring me 💖

Become a sponsor 🚀
If you find recma-mdx-interpolate useful in your projects, consider supporting my work.
Your sponsorship means a lot 💖
My sponsors are going to be featured here and on my sponsor wall.
A warm thanks 🙌 to @ErfanEbrahimnia, @recepkyk, and @LSeaburg for the support!
Thank you for supporting open source! 🙌
recma-mdx-interpolate
This package is a unified (recma) plugin that enables interpolation of identifiers wrapped in curly braces within the alt, src, href, and title attributes of markdown link and image syntax; and wrapped in special syntax within markdown code fences in MDX/markdown.
unified is a project that transforms content with abstract syntax trees (ASTs) using the new parser micromark. recma adds support for producing a javascript code by transforming esast which stands for Ecma Script Abstract Syntax Tree (AST) that is used in production of compiled source for the MDX.
Installation
This package is suitable for ESM only. In Node.js (version 18+), install with npm:
npm install recma-mdx-interpolateor
yarn add recma-mdx-interpolateUsage
Say we have the following file, example.mdx,
[{props.email}](mailto:{props.email} "{props.title}")
And our module, example.js, looks as follows:
import { read } from "to-vfile";
import { compile } from "@mdx-js/mdx";
import recmaMdxInterpolate from "recma-mdx-interpolate";
main();
async function main() {
const source = await read("example.mdx");
const compiledSource = await compile(source, {
recmaPlugins: [recmaMdxInterpolate],
});
return String(compiledSource);
}Now, running node example.js produces the compiled source like below:
// ...
function _createMdxContent(props) {
// ...
return _jsxs(_Fragment, {
children: [_jsx(_components.p, {
children: _jsx(_components.a, {
- href: "mailto:%7Bprops.email%7D",
+ href: `mailto:${props.email}`,
- title: "{props.title}",
+ title: props.title,
children: props.email
})
}), "\n", _jsx(_components.p, {
children: _jsx(_components.img, {
- src: "%7Bsrc%7D",
+ src: src,
- alt: "{alt}",
+ alt: alt,
- title: "{title}"
+ title: title,
})
})]
});
}
// ...This is roughly equivalent JSX with:
export default function MDXContent() {
return (
<p>
<a href={`mailto:${props.email}`} title={props.title}>{props.email}</a>
</p>
<p>
<img alt={alt} src={src} title={title} />
</p>
)
}The facts
Normally, interpolation of an identifier (identifiers wrapped with curly braces, for example {name} or {props.src}) within markdown content doesn't work. The interpolation of an identifier, in other words MDX expressions, is a matter of MDX.
MDX expressions in some places is not viable since MDX is not a template language. For example, MDX expressions within markdown link, image and code fence syntax doesn't work in MDX. recma-mdx-interpolate patches that gaps !
If your integration supports "md" format, like (@mdx-js/mdx, next-mdx-remote-client etc.), then recma-mdx-interpolate can be used for "md" format as well.
Considerations for Markdown Link Syntax
Syntax: [text part](href part "title part")
Href part of a markdown link is URI encoded, meaningly curly braces are encoded as %7B and %7D. The interpolation doesn't work by default. So, recma-mdx-interpolate handles that part.
Title part of a markdown link remains as-is. The interpolation doesn't work by default. So, recma-mdx-interpolate handles that part as well.
Text part of a markdown link is parsed as inline markdown, which means it can be broken into child nodes like strong, emphasis, inlineCode, or plain text. In MDX, these nodes support interpolation by default, so recma-mdx-interpolate doesn’t need to handle them. In plain markdown (md), although the text is also parsed into children, those nodes do not support interpolation by default, so recma-mdx-interpolate takes care of it.
Considerations for Markdown Image Syntax
Syntax: 
Src part of a markdown image is URI encoded, meaningly curly braces are encoded as %7B and %7D. The interpolation doesn't work by default. So, recma-mdx-interpolate handles that part.
Title part of a markdown image remains as-is. The interpolation doesn't work by default. So, recma-mdx-interpolate handles that part as well.
Alt part of a markdown image behaves differently in MDX compared to standard markdown:
- In markdown, the alt text is treated as plain text, and curly braces
{}are preserved; sorecma-mdx-interpolatehandles the interpolation. - In MDX, however, curly braces are automatically stripped from the alt text. This means standard interpolation like
doesn't work — there's no way to detect the interpolation syntax after MDX parsing. Since curly braces are stripped, we need to find a workaround for MDX.
Workaround for Interpolation in alt part of markdown image in MDX
To enable interpolation in the alt part of an image in MDX, use the following workaround:
- Use double curly braces:
This works if alt is a simple identifier (e.g., a variable name like my_alt or altText).
For object paths (e.g., image.alt, props.image.alt), double curly braces alone cause the internal MDX parser (acorn) to throw an Unexpected token error. To work around this:
- Use prefix before object path with any alphanumeric identifier (or underscore), followed by a colon
:


This format is recognized and handled by recma-mdx-interpolate plugin.
Note: The colon
:is essential — other separators (like@,-, or%) do not work.
As a summary,
- In markdown:
Use standard interpolation like
or - In MDX:
Use double curly braces:
for simple variablesfor object paths (required workaround)
This is a weird workaround, but nothing to do else due to internal MDX parsing, and the double-curly-braces-and-colon-based workaround is the only known reliable method after extensive testing. The workaround is for MDX, not for markdown in which curly braces are not removed.
Considerations for Markdown Code Fence Syntax
recma-mdx-interpolation supports interpolation in code fence children (the content of code blocks). By default, code fence interpolation uses double curly braces {{ }}:
```bash
pnpm add @mdx-js/loader@{{ props.version-name }}
```However, if your code fence language already uses {{ }} syntax, you can customize the interpolation syntax using the interpolationSyntaxForCodeFence option to avoid conflicts. See the interpolationSyntaxForCodeFence option for details.
Important: Code fence interpolation behaves differently from interpolation in other attributes:
- Code fences: Use configurable syntax (default
{{ }}) - Other attributes (href, src, alt, title): Use single curly braces
{}(cannot be customized)
When should I use this?
markdown images and links
If you want to interpolate identifiers within alt, src, href, and title attributes of an image and link constructed with markdown syntax, such as:
[{props.email}](mailto:{props.email} "{props.title}")
Here are some explanations I should emphasise:
recma-mdx-interpolateworks for only href/title parts of a markdown link and alt/src/title parts of a markdown image in "md" and "mdx" format, additionally text part of a link in "md" format.The text part of a link (the children of an anchor) is already interpolated in MDX, so
recma-mdx-interpolatedoes not touch it[{already.interpolated}](...)if the format is "mdx".The curly braces in the alt of an image are removed during remark-mdx parsing in MDX (not in markdown format). So you need to use aferomentioned workaround if the format is "mdx".
If you are using a plugin (like
rehype-image-toolkit) to convert image syntax to video/audio, thenrecma-mdx-interpolatealso supports src/title of a<video>/<audio>elements; and src of a<source>element.
markdown code fences
If you want to interpolate identifiers within code fence constructed with markdown syntax, such as:
```bash
pnpm add @mdx-js/loader@{{props.version}}
```The identifiers can be just a javascript identifier or object notation. recma-mdx-interpolate can handles them correctly.
{{ name }}
↓
${name}
{{ npm.name }}
↓
${npm.name}
{{ npm.version-name }}
↓
${npm["version-name"]}recma-mdx-interpolate does not capture invalid javascript identifier or object notation; and keeps the text as it is.
{{ my-name }}
❌ keeps it as it is
{{ my-name }}
{{ node-name.version }}
❌ keeps it as it is
{{ node-name.version }}
{{ loader }} {{ loader-version }}
✅ ❌ captures the first one, and keeps the second as it is
${loader} {{ loader-version }}[!IMPORTANT] You should provide the value of identifiers in your integration (via frontmatter; or
propsin@mdx-js/mdx; orscopeinnext-mdx-remote-clientandnext-mdx-remoteetc).
The list of the tags and attributes that recma-mdx-interpolate processes
|tags |with "mdx" format |with "md" format |
|------------------|------------------------------------------------|-----------------------------------------------|
| <a> (link) | href, title |text (children), href, title |
| <img> | alt*, src, title |alt, src, title |
| <video> | src, title |src, title |
| <audio> | src, title |src, title |
| <source> | src |src |
| <code> | content (children) |content (children). |
Note *: works with a workaround only.
Note **: works with a special syntax apart from single curly braces
recma-mdx-interpolate supports html in markdown
It supports html syntax besides markdown syntax for link and image and code fence in markdown contents. In MDX, the plugin doesn't touch these html syntax (actually MdxJsx elements) due to mdx parser handles interpolations already.
<a href={link.href} title={link.title}>{link.text}</a>
<img src={image.src} alt={image.alt} title={image.title}>
<pre><code className="language-bash">
pnpm add {{name}}@{{node.version}} /* {{node.version-name}} */
</code></pre>As you pay attention, there is no self-closing slash in <img> element above. Because there is no need self-closing slash for self-closable html elements in markdown (md) format, and of course in HTML5 standard. But, if you want to put a self-closing slash, put a space before it. Otherwise, the plugin infers that the self-closing slash belongs to the last attribute value, and produce unneccesary "/" in the value of the last attribute.
<img src={image.src} alt={image.alt} title={image.title}> it is okey
<img src={image.src} alt={image.alt} title={image.title} /> it is okey
<img src={image.src} alt={image.alt} title={image.title}/> it is okey but the plugin infers "/" belongs the title.Options
All options are optional and have default values.
export type InterpolateOptions = {
disable?: boolean;
exclude?: Partial<Record<"a" | "img" | "video" | "audio" | "source" | "code", string | string[] | true>>
interpolationSyntaxForCodeFence?: string;
strict?: boolean;
};disable
It is a boolean option to disable the plugin completely.
It is false by default.
It could be useful if you want the plugin NOT to work when the format is not "mdx".
use(recmaMdxInterpolate, { disable: format !== "mdx" } as InterpolateOptions);exclude
It is an object option to exlude some tags and attributes.
Default is empty object {} meaningly there is no excluded tags and attributes, all targets is going to be processed.
use(recmaMdxInterpolate, { exclude: {a: true} } as InterpolateOptions);Now, the links (anchor <a>) will be excluded from processing.
use(recmaMdxInterpolate, { exclude: {img: ["src"]} } as InterpolateOptions);Now, the src attribute of images will be excluded from processing.
interpolationSyntaxForCodeFence
It is a string option to customize the interpolation syntax used in code fences only.
Default is "{{" (double curly braces).
Note: This option affects only code fences (
<code>elements). For all otherimageandlinkattributes (href,src,alt,title), the interpolation syntax is single curly braces{}and cannot be customized.
Why use this option?
Some code fence languages have their own syntax that conflicts with {{ }}. By setting a different opening syntax, the plugin automatically mirrors it for the closing syntax:
{{mirrors to}}[[mirrors to]]<<:mirrors to:>>{%mirrors to%}
This intelligent mirroring allows flexibility while keeping the configuration simple.
Examples:
// Default behavior - uses {{ and }}
use(recmaMdxInterpolate);
// Custom syntax using angle brackets with colon
use(recmaMdxInterpolate, { interpolationSyntaxForCodeFence: "<<:" });Now you can write in code fences, accordingly:
```bash
pnpm add @mdx-js/loader@{{props.version}}
```
```bash
pnpm add @mdx-js/loader@<<:props.version:>>
```strict
It is a boolean option to enforce strict interpolation syntax (only in code fences).
It is false by default.
It could be useful if you want to avoid accidental interpolation and require expressions to appear exactly between the delimiters without surrounding whitespace. When strict is enabled, the expression must not contain leading or trailing whitespace inside the delimiters.
use(recmaMdxInterpolate, { strict: true } as InterpolateOptions);Now, the plugin is going to behave:
## valid examples
+ {{name}}
+ {{props.version}}
+ {{npm.version-name}}
## Invalid examples (will not be captured)
+ {{ name }}
+ {{ name}}
+ {{name }}
+ {{ props.version }}
+ {{ props.version}}
+ {{props.version }}Syntax tree
This plugin only modifies the ESAST (Ecma Script Abstract Syntax Tree) as explained.
Types
This package is fully typed with TypeScript. The plugin options is exported as InterpolateOptions.
Compatibility
This plugin works with unified version 6+. It is compatible with mdx version 3+.
Security
Use of recma-mdx-interpolate does not involve user content so there are no openings for 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 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)remark-mdx-remove-expressions– Remark plugin to remove MDX expressions within curlybraces {} in MDX content
My Rehype Plugins
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.
My Recma Plugins
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.
My Unist Utils and Plugins
I also build low-level utilities and plugins for the Unist ecosystem that can be used across Remark, Rehype, Recma, and other syntax trees.
unist-util-find-between-all– Unist utility to find the nodes between two nodes.unist-plugin-log-tree– Debugging plugin for the unified ecosystem that logs abstract syntax trees (ASTs) without transforming.
License
MIT License © ipikuka
