Marked.js Markdown in Chrome Extensions: Complete Implementation Guide

23 min read

Marked.js Markdown in Chrome Extensions: Complete Implementation Guide

Marked.js Markdown in Chrome Extensions: Complete Implementation Guide

If you are building a Chrome extension that needs to display Markdown content, you have probably discovered that raw Markdown is difficult for users to read. Whether you are creating an MD viewer extension, building a documentation reader, or developing a note-taking app that works with Markdown files, rendering Markdown properly is essential for providing a great user experience. This is where Marked.js comes in—a fast, reliable, and feature-rich Markdown parser that integrates seamlessly with Chrome extensions.

In this comprehensive guide, we will explore everything you need to know about using Marked.js in Chrome extensions. From basic setup to advanced customization, security considerations, and performance optimization, you will learn how to build professional-grade Markdown rendering into your extension.


What is Marked.js and Why Use It in Chrome Extensions

Marked.js is a low-level Markdown parser that converts Markdown text into HTML. Unlike some other Markdown libraries that focus on being full-fledged editors, Marked.js concentrates on doing one thing exceptionally well: parsing Markdown quickly and accurately. This makes it an ideal choice for Chrome extensions where performance and reliability are paramount.

The library has become one of the most popular Markdown parsers in the JavaScript ecosystem for several compelling reasons. First, it is incredibly fast, capable of parsing thousands of lines of Markdown in milliseconds. Second, it supports the full CommonMark specification plus many useful extensions. Third, it has a tiny footprint compared to alternatives while still offering extensive customization options. Fourth, it works perfectly in browser environments, making it naturally suited for Chrome extension development.

When you are building a Markdown render Chrome extension, choosing the right parser significantly impacts user experience. Marked.js provides consistent results across different Markdown inputs, supports GitHub Flavored Markdown (GFM) out of the box, and includes built-in syntax highlighting support through integration with Highlight.js or Prism. These features make it the go-to choice for developers building MD viewer extensions.

Key Features of Marked.js

Marked.js offers an impressive array of features that make it perfect for Chrome extension development. The library supports the complete CommonMark specification, ensuring that your extension renders Markdown exactly as users would expect from standard Markdown processors. It also supports GitHub Flavored Markdown, which adds tables, task lists, strikethrough, and autolinks—features that users increasingly expect in modern Markdown applications.

The library includes smart typography extensions that automatically convert straight quotes to curly quotes, dashes to em-dashes, and other text transformations that make rendered content look more professional. Additionally, Marked.js supports custom extensions, allowing you to add your own Markdown syntax or modify how existing elements are processed.


Setting Up Marked.js in Your Chrome Extension Project

Getting Marked.js up and running in your Chrome extension is straightforward. There are several approaches you can take, each with its own advantages depending on your project structure and requirements.

Installation Methods

The simplest way to add Marked.js to your Chrome extension is through npm if you are using a build process. Run the following command in your project directory:

npm install marked

This downloads the Marked.js package and adds it to your package.json dependencies. You can then import it into your JavaScript files using ES6 imports or CommonJS require statements, depending on your build configuration.

Alternatively, you can include Marked.js directly in your extension by downloading the standalone browser build. The file marked.min.js is available from the official Marked.js repository and can be included in your extension’s HTML files using a standard script tag. If you look in the chrome-extension-guide repository, you will find a copy of marked.min.js already included, which you can reference directly in your extension files.

For this guide, we will assume you are including Marked.js directly in your extension using the script tag approach, as this requires no build configuration and works immediately.

Including Marked.js in Your Manifest

When using Marked.js in your Chrome extension, you need to ensure it is properly included in your extension bundle. If you are including the library as a separate file, add it to your files array in the web_accessible_resources section of your manifest.json if you plan to load it dynamically, or simply include it alongside your other JavaScript files.

Here is a basic example of how your manifest.json might look with Marked.js included:

{
  "manifest_version": 3,
  "name": "Markdown Viewer",
  "version": "1.0",
  "description": "View Markdown files in your browser",
  "permissions": ["fileSystem", "activeTab"],
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["marked.min.js", "content.js"]
  }],
  "action": {
    "default_popup": "popup.html"
  }
}

The key point is to ensure that marked.min.js loads before any script that uses it. By listing it first in the content_scripts array, you guarantee that the Marked library is available when your content script executes.


Building a Basic Markdown Viewer Extension

Now that you understand how to set up Marked.js, let us build a functional Markdown viewer extension. This example will demonstrate the core concepts you need to understand before building more complex MD viewer extensions.

The Content Script

Your content script is where the Markdown rendering happens. In a Chrome extension using Manifest V3, content scripts run in the context of web pages and can access and modify page content. Here is a basic content script that uses Marked.js to render Markdown:

// content.js
function renderMarkdown() {
  // Find all elements that contain Markdown content
  const markdownElements = document.querySelectorAll('.markdown-content');
  
  markdownElements.forEach(element => {
    const markdownText = element.textContent;
    const html = marked.parse(markdownText);
    element.innerHTML = html;
  });
}

// Run when the page loads
document.addEventListener('DOMContentLoaded', renderMarkdown);

// Also run immediately in case DOM is already loaded
if (document.readyState !== 'loading') {
  renderMarkdown();
}

This script looks for elements with the class “markdown-content” and converts their text content from Markdown to HTML using Marked.js. The rendered HTML then replaces the original text, giving users a properly formatted view of the Markdown content.

The Popup Interface

Most Markdown viewer extensions include a popup that users can open to view rendered content or configure settings. Here is a simple popup that demonstrates how to integrate Marked.js with a popup interface:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Markdown Viewer</title>
  <style>
    body {
      width: 400px;
      padding: 16px;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }
    textarea {
      width: 100%;
      height: 150px;
      margin-bottom: 12px;
      padding: 8px;
      border: 1px solid #ccc;
      border-radius: 4px;
      font-family: monospace;
    }
    button {
      background: #4285f4;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 4px;
      cursor: pointer;
    }
    button:hover {
      background: #3367d6;
    }
    #output {
      margin-top: 16px;
      padding: 12px;
      border: 1px solid #eee;
      border-radius: 4px;
      background: #f9f9f9;
    }
  </style>
</head>
<body>
  <h2>Markdown Preview</h2>
  <textarea id="markdown-input" placeholder="Enter Markdown here..."># Hello World

This is **bold** and this is *italic*.

- List item 1
- List item 2

[Link text](https://example.com)</textarea>
  <button id="render-btn">Render Markdown</button>
  <div id="output"></div>
  
  <script src="marked.min.js"></script>
  <script src="popup.js"></script>
</body>
</html>

The popup provides a textarea for users to enter Markdown and displays the rendered output in real-time when they click the render button. This gives users immediate feedback on how their Markdown will appear.

The Popup JavaScript

The popup JavaScript connects the user interface to Marked.js:

// popup.js
document.addEventListener('DOMContentLoaded', function() {
  const input = document.getElementById('markdown-input');
  const output = document.getElementById('output');
  const button = document.getElementById('render-btn');
  
  function render() {
    const markdown = input.value;
    const html = marked.parse(markdown);
    output.innerHTML = html;
  }
  
  button.addEventListener('click', render);
  
  // Also render on input for live preview
  input.addEventListener('input', render);
  
  // Initial render
  render();
});

This script listens for button clicks and input events, passing the Markdown text to Marked.js and displaying the resulting HTML in the output div. The live preview feature provides an excellent user experience, showing users exactly how their Markdown will look as they type.


Advanced Marked.js Configuration

While the basic setup works well for simple use cases, Marked.js offers extensive configuration options that allow you to customize its behavior to match your specific requirements.

Customizing Marked Options

Marked.js provides a comprehensive options object that controls how Markdown is parsed and rendered. Here is an example demonstrating common customization options:

// Configure Marked with custom options
marked.setOptions({
  // Enable GitHub Flavored Markdown
  gfm: true,
  
  // Enable breaks (convert \n to <br>)
  breaks: true,
  
  // Specify the parser engine
  parser: null, // Use default parser
  
  // Custom renderer (explained below)
  renderer: null,
  
  // Enable synchronous parsing (faster for small documents)
  async: false
});

// Parse with custom options
const html = marked.parse(markdownText, {
  breaks: true,
  gfm: true
});

These options give you fine-grained control over how Marked.js processes Markdown. The gfm option enables GitHub Flavored Markdown features, while breaks controls whether newlines are converted to line breaks.

Using Custom Renderers

One of Marked.js most powerful features is the ability to create custom renderers that modify how specific Markdown elements are converted to HTML. This is particularly useful for Chrome extensions that need to add custom styling or functionality:

const renderer = new marked.Renderer();

// Custom link rendering
renderer.link = function(href, title, text) {
  // Add target="_blank" to all links and add security attributes
  const titleAttr = title ? ` title="${title}"` : '';
  return `<a href="${href}" target="_blank" rel="noopener noreferrer"${titleAttr}>${text}</a>`;
};

// Custom image rendering
renderer.image = function(href, title, text) {
  const titleAttr = title ? ` title="${title}"` : '';
  return `<img src="${href}" alt="${text}"${titleAttr} class="md-image">`;
};

// Custom heading rendering with anchor links
renderer.heading = function(text, level, raw) {
  const slug = raw.toLowerCase().replace(/[^\w]+/g, '-');
  return `<h${level} id="${slug}">${text}</h${level}>`;
};

// Use the custom renderer
marked.setOptions({ renderer: renderer });
const html = marked.parse(markdownText);

This custom renderer demonstrates several important enhancements. Links automatically get security attributes to prevent tabnabbing vulnerabilities. Images receive a CSS class for consistent styling. Headings get ID attributes that enable anchor linking—particularly useful for documentation-style extensions.

Syntax Highlighting Integration

For code blocks in your Markdown, syntax highlighting significantly improves readability. Marked.js integrates easily with Highlight.js, which is already included in the chrome-extension-guide repository:

// Initialize Highlight.js
marked.setOptions({
  highlight: function(code, lang) {
    const language = hljs.getLanguage(lang) ? lang : 'plaintext';
    return hljs.highlight(code, { language: language }).value;
  },
  langPrefix: 'hljs language-'
});

// Parse with syntax highlighting
const html = marked.parse(markdownText);

The code above configures Marked.js to use Highlight.js for code block syntax highlighting. When users view Markdown with code blocks in your extension, the code will be color-coded based on the detected or specified programming language.


Security Considerations for Markdown Rendering

When building an extension that renders Markdown as HTML, security should be your top priority. Improperly sanitized HTML can lead to cross-site scripting (XSS) vulnerabilities that compromise user data or allow attackers to execute malicious scripts.

Understanding the XSS Risk

Marked.js itself does not sanitize HTML. This means if your Markdown input includes raw HTML—either intentionally or through malicious injection— that HTML will be included verbatim in the output. A user or attacker could include script tags or other dangerous HTML that executes in the context of your extension.

For example, consider this seemingly innocent Markdown:

Click [here](javascript:alert('XSS')) to win!

Or worse:

<img src="x" onerror="alert('XSS')">

Both examples could execute arbitrary JavaScript in your extension, potentially stealing cookies, session tokens, or other sensitive data.

Implementing HTML Sanitization

To protect against XSS attacks, you must sanitize the HTML output from Marked.js before rendering it. The DOMPurify library is the gold standard for HTML sanitization in browser environments:

// Include DOMPurify in your extension
<script src="dompurify/dist/purify.min.js"></script>

// Sanitize the output from Marked.js
function safeRender(markdown) {
  // First, parse Markdown to HTML
  const rawHtml = marked.parse(markdown);
  
  // Then sanitize to remove dangerous content
  const cleanHtml = DOMPurify.sanitize(rawHtml, {
    // Allow specific elements
    ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 
                   'strong', 'em', 'code', 'pre', 'blockquote',
                   'ul', 'ol', 'li', 'a', 'img', 'table', 'thead',
                   'tbody', 'tr', 'th', 'td', 'hr', 'span', 'div'],
    
    // Allow specific attributes
    ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'id', 
                   'target', 'rel', 'width', 'height'],
    
    // Add rel attributes to links for security
    ADD_ATTR: ['target', 'rel'],
    
    // Force URLs to use safe protocols
    ADD_TAGS: ['iframe']
  });
  
  return cleanHtml;
}

// Use the safe render function
const html = safeRender(userMarkdown);

This sanitization configuration allows common Markdown elements while blocking potentially dangerous content. The ALLOWED_TAGS and ALLOWED_ATTR arrays define exactly what HTML elements and attributes are permitted. By excluding script, iframe, and other dangerous tags, you prevent XSS attacks while still providing rich formatting.

Content Security Policy

Chrome extensions have a Content Security Policy (CSP) that restricts what resources can be loaded and what scripts can execute. When building a Markdown viewer extension, ensure your CSP allows necessary functionality while maintaining security.

In your manifest.json, you can configure CSP:

{
  "content_security_policy": {
    "extension_pages": "script-src 'self' 'unsafe-inline'; object-src 'self'; img-src 'self' data: https:;"
  }
}

This policy allows your extension to run its own scripts and inline scripts while restricting external resources. Adjust these settings based on your specific needs, but be cautious about adding ‘unsafe-eval’ or overly permissive settings.


Performance Optimization

When building Chrome extensions that process Markdown, performance is crucial—especially if your extension handles large documents or processes many files. Here are strategies for optimizing your Marked.js implementation.

Parsing Large Documents

For large Markdown documents, parsing can take noticeable time. Marked.js is already highly optimized, but there are additional steps you can take to improve performance:

// Use Web Workers for parsing large documents
function parseMarkdownInWorker(markdown) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('markdown-worker.js');
    
    worker.onmessage = function(e) {
      resolve(e.data);
      worker.terminate();
    };
    
    worker.onerror = function(e) {
      reject(e);
      worker.terminate();
    };
    
    worker.postMessage(markdown);
  });
}

// Worker script (markdown-worker.js)
// importScripts('marked.min.js');
// self.onmessage = function(e) {
//   const html = marked.parse(e.data);
//   self.postMessage(html);
// };

By moving Markdown parsing to a Web Worker, you prevent the parsing operation from blocking the main thread and freezing the user interface. This is particularly useful when processing documents with thousands of lines.

Caching Parsed Results

If your extension displays the same Markdown content repeatedly, caching the parsed HTML avoids redundant processing:

const htmlCache = new Map();
const CACHE_MAX_SIZE = 50;

function getCachedHtml(markdown) {
  const hash = simpleHash(markdown);
  
  if (htmlCache.has(hash)) {
    return htmlCache.get(hash);
  }
  
  const html = marked.parse(markdown);
  
  // Implement cache size limits
  if (htmlCache.size >= CACHE_MAX_SIZE) {
    const firstKey = htmlCache.keys().next().value;
    htmlCache.delete(firstKey);
  }
  
  htmlCache.set(hash, html);
  return html;
}

// Simple hash function for cache keys
function simpleHash(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash;
  }
  return hash;
}

This caching mechanism stores parsed HTML in memory, keyed by a simple hash of the original Markdown. When the same content is requested again, the cached HTML is returned immediately without re-parsing.


Real-World Use Cases and Examples

Now that you understand the fundamentals, let us explore some real-world use cases for Marked.js in Chrome extensions.

Building a GitHub README Viewer

One popular use case for Markdown rendering in Chrome extensions is viewing README files on GitHub and other platforms. GitHub already renders Markdown, but if you want additional features or a custom viewing experience, you can build an extension that enhances the default rendering:

// Content script for GitHub README enhancement
function enhanceReadme() {
  const readmeContainer = document.querySelector('.markdown-body');
  
  if (!readmeContainer) return;
  
  // Get the original Markdown if available, or extract from rendered HTML
  // Then re-render with custom styling and features
  
  const currentHtml = readmeContainer.innerHTML;
  // Apply custom enhancements like:
  // - Table of contents generation
  // - Copy code buttons
  // - Custom typography
  // - Dark mode improvements
}

Creating a Local MD File Viewer

Another common use case is viewing local Markdown files. Chrome extensions can use the File System Access API to read local files and render them:

// Popup script for local file viewing
async function openLocalFile() {
  try {
    const [fileHandle] = await window.showOpenFilePicker({
      types: [{
        description: 'Markdown Files',
        accept: {
          'text/markdown': ['.md', '.markdown', '.mdown'],
          'text/plain': ['.txt']
        }
      }]
    });
    
    const file = await fileHandle.getFile();
    const markdown = await file.text();
    
    // Parse and display
    const html = marked.parse(markdown);
    document.getElementById('viewer').innerHTML = html;
    
  } catch (err) {
    console.error('Error opening file:', err);
  }
}

Building a Documentation Reader

If you are building developer tools or libraries, a documentation reader extension that renders Markdown files from your documentation site provides excellent value to users:

// Background script for documentation fetching
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'fetchDoc') {
    fetch(request.url)
      .then(response => response.text())
      .then(markdown => {
        const html = marked.parse(markdown);
        sendResponse({ success: true, html: html });
      })
      .catch(error => {
        sendResponse({ success: false, error: error.message });
      });
    return true; // Keep channel open for async response
  }
});

Best Practices and Common Pitfalls

As you build your Marked.js-powered Chrome extension, keep these best practices in mind to avoid common mistakes and ensure a polished final product.

First, always sanitize user input before rendering. Never trust Markdown from untrusted sources without sanitization, as it could contain malicious scripts or HTML. Second, test your extension with various Markdown inputs, including edge cases like deeply nested lists, large code blocks, and unusual character encodings. Third, provide clear feedback to users when Markdown parsing fails or produces unexpected results.

Fourth, consider accessibility when rendering Markdown. Use semantic HTML elements, provide alt text for images, and ensure that rendered content is readable by screen readers. Fifth, handle async operations gracefully. If your extension fetches Markdown from remote sources, show loading states and handle errors appropriately.

Finally, keep your Marked.js library updated. The library is actively maintained, and updates often include bug fixes, security patches, and performance improvements. Regularly check for new versions and update the copy included in your extension.


Conclusion

Integrating Marked.js into your Chrome extension provides powerful Markdown rendering capabilities that can transform your extension from a simple tool into a professional-grade application. Whether you are building an MD viewer extension, a documentation reader, or any application that works with Markdown content, Marked.js offers the performance, flexibility, and reliability you need.

Remember to prioritize security by always sanitizing HTML output, optimize performance for large documents using Web Workers and caching, and take advantage of Marked.js customization options to create a unique user experience. With the foundation provided in this guide, you are well-equipped to build sophisticated Markdown-powered Chrome extensions that your users will love.

The combination of Marked.js fast parsing, extensive configuration options, and seamless browser integration makes it the ideal choice for Chrome extension development. Start building your Markdown viewer extension today, and provide your users with the rich, beautifully rendered Markdown content they expect.

No previous article
No next article