How to Embed CodePen in Markdown: The 30-Seconds-of-Code Build Pipeline
The 30-seconds-of-code repository automatically converts standalone CodePen URLs into interactive embeds using a custom remark plugin that transforms markdown AST nodes into HTML figure elements.
The 30-seconds-of-code project handles embedding CodePen in markdown through a sophisticated build pipeline that parses content into an abstract syntax tree before generating HTML. This approach allows content creators to simply paste a CodePen URL on its own line without manually writing complex embed code, while the build process handles the transformation automatically.
The Markdown Processing Pipeline for CodePen Embeds
The transformation from a plain URL to an interactive widget occurs across four distinct stages within the content processing system.
Step 1: Parsing Markdown to Remark AST
The raw markdown content first passes through remark-parse combined with remark-gfm to generate a remark abstract syntax tree. This AST represents the document structure as a hierarchy of nodes, where standalone URLs appear as link nodes nested within paragraph parents.
Step 2: Transforming Links with embedCodepensFromLinks
Before the AST converts to HTML, the custom plugin embedCodepensFromLinks traverses the tree. Located at src/lib/contentUtils/markdownParser/plugins/ast/embedCodepensFromLinks.js, this plugin specifically targets link nodes whose parent is a paragraph.
When the plugin encounters a URL matching the regex pattern /^https?:\/\/codepen\.io\/([^/]+)\/pen\/([^/]+)\/?$/, it extracts the username and slug, then replaces the entire parent paragraph with an HTML figure element containing the CodePen embed markup.
// src/lib/contentUtils/markdownParser/plugins/ast/embedCodepensFromLinks.js
const codepenRegexp = /^https?:\/\/codepen\.io\/([^/]+)\/pen\/([^/]+)\/?$/;
…
if (match) {
const [, user, slug] = match;
parentNode.type = `html`;
parentNode.value = `<figure class="${className}">
<p class="codepen" ${dataAttrString} data-slug-hash="${slug}" data-user="${user}">
<span>See the <a href="https://codepen.io/${user}/pen/${slug}" target="_blank" rel="noopener noreferrer">embedded CodePen</a></span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
</figure>`;
}
Step 3: Converting to HTML with remark-rehype
The modified AST flows into remark-rehype with allowDangerousHtml: true enabled. This converts the remark tree to a hast (HTML AST) while preserving the raw HTML nodes injected by the embed plugin. Since the CodePen markup is already valid HTML, it passes through unchanged during this transformation.
Step 4: HTML Serialization
Finally, rehype-stringify serializes the hast into a string of HTML. The resulting output contains the fully functional CodePen embed ready for browser rendering, complete with the asynchronous script loader and data attributes controlling the presentation.
How the embedCodepensFromLinks Plugin Works
The plugin operates as an AST transformer within the unified ecosystem. It specifically checks for link nodes where the URL matches the CodePen pattern and the node sits directly inside a paragraph.
Key implementation details:
- Regex matching: Uses
/^https?:\/\/codepen\.io\/([^/]+)\/pen\/([^/]+)\/?$/to validate URLs and capture the username and pen slug - Node replacement: Replaces the parent
paragraphnode entirely rather than just the link, ensuring the embed stands alone as a block-level element - Data attributes: Injects configuration attributes like
data-slug-hash,data-user, and theme controls directly into the HTML - Script injection: Appends the CodePen embed script (
https://cpwebassets.codepen.io/assets/embed/ei.js) to activate the widget
Notably, the plugin intentionally omits the data-preview="true" attribute because it conflicts with the site's custom styling requirements.
Configuration in the MarkdownParser Class
The entire pipeline is orchestrated within the MarkdownParser class located at src/lib/contentUtils/markdownParser/markdownParser.js. Here, the plugins are chained in a specific order to ensure proper transformation:
// src/lib/contentUtils/markdownParser/markdownParser.js
.use(highlightCode, { grammars, codeHighlighter })
.use(embedCodepensFromLinks, { className: 'codepen-wrapper' })
.use(transformArticleEmbeds)
.use(remarkRehype, { allowDangerousHtml: true })
The embedCodepensFromLinks plugin receives a configuration object specifying the CSS class name (codepen-wrapper) applied to the wrapping figure element. This placement in the chain ensures that CodePen links are transformed before the AST converts to HTML, but after syntax highlighting processes code blocks.
Example: From Markdown URL to Embedded Widget
Input markdown:
#### CodePen embeds
https://codepen.io/chalarangelo/pen/mdodgeL
Output HTML after processing:
<figure class="codepen-wrapper">
<p class="codepen"
data-height="100%"
data-theme-id="dark"
data-default-tab="result"
data-border-color="#07071c"
data-border="none"
data-tab-bar-color="#161632"
data-tab-link-color="#e3e3e8"
data-active-link-color="#8bb7fe"
data-active-tab-accent-color="#5394fd"
data-active-tab-color="#18203a"
data-tab-color="#18203a"
data-slug-hash="mdodgeL"
data-user="chalarangelo">
<span>See the <a href="https://codepen.io/chalarangelo/pen/mdodgeL"
target="_blank"
rel="noopener noreferrer">embedded CodePen</a></span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
</figure>
The resulting embed loads asynchronously, displaying the interactive CodePen demo with the dark theme and result tab active by default.
Key Files in the Embedding System
src/lib/contentUtils/markdownParser/plugins/ast/embedCodepensFromLinks.js– Contains the core transformation logic that identifies CodePen URLs and generates the embed HTML.src/lib/contentUtils/markdownParser/markdownParser.js– Orchestrates the unified pipeline, configuring the embed plugin with CSS class names and execution order.src/lib/contentUtils/markdownParser/plugins/index.js– Aggregates and exports all remark plugins, making the CodePen embedder available to the parser.spec/lib/contentUtils/contentUtils.test.js– Validates that the URL-to-embed transformation works correctly across various input formats.
Summary
- The 30-seconds-of-code project handles embedding CodePen in markdown through a custom remark plugin called
embedCodepensFromLinks. - The plugin scans the AST for paragraph nodes containing solitary CodePen URLs matching the pattern
^https?://codepen\.io/([^/]+)/pen/([^/]+)/?$. - Valid URLs trigger replacement of the parent paragraph with an HTML
figureelement containing the CodePen embed markup and asynchronous loader script. - The pipeline processes content through
remark-parse→embedCodepensFromLinks→remark-rehype→rehype-stringifyto generate the final HTML. - Configuration occurs in
src/lib/contentUtils/markdownParser/markdownParser.js, where the plugin receives styling classes likecodepen-wrapper.
Frequently Asked Questions
What regex pattern does the plugin use to identify CodePen URLs?
The plugin uses the pattern /^https?:\/\/codepen\.io\/([^/]+)\/pen\/([^/]+)\/?$/ to match standard CodePen URLs. This regex captures the username in the first capture group and the pen slug in the second, which the plugin then injects into the data-user and data-slug-hash attributes of the embed markup.
Why does the plugin replace the parent paragraph node instead of just the link node?
The plugin replaces the entire parent paragraph node to ensure the embed renders as a block-level element. In markdown ASTs, a standalone URL typically exists as a link node inside a paragraph node. By replacing the paragraph with an HTML figure element, the plugin maintains semantic HTML structure and prevents the embed from being wrapped in unwanted paragraph tags that could break layout or styling.
Does the embed include a data-preview attribute?
No, the plugin intentionally omits the data-preview="true" attribute from the generated HTML. According to comments in the source code at src/lib/contentUtils/markdownParser/plugins/ast/embedCodepensFromLinks.js, this attribute is excluded because it conflicts with the site's custom styling requirements. Instead, the embed relies on other data-* attributes to control the theme, tab behavior, and visual presentation.
How can developers test that CodePen URLs are being transformed correctly?
Developers can verify the transformation logic through the unit tests located in spec/lib/contentUtils/contentUtils.test.js. These tests validate that the embedCodepensFromLinks plugin correctly identifies valid CodePen URLs, extracts the username and slug, and generates the expected HTML output with proper data attributes and the asynchronous loader script. Running these tests ensures that changes to the regex or HTML template do not break existing embed functionality.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →