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.

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.

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 paragraph node 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

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 figure element containing the CodePen embed markup and asynchronous loader script.
  • The pipeline processes content through remark-parseembedCodepensFromLinksremark-rehyperehype-stringify to generate the final HTML.
  • Configuration occurs in src/lib/contentUtils/markdownParser/markdownParser.js, where the plugin receives styling classes like codepen-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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →