Mastering Marked.js: A Guide to Adding Custom Classes to Tables (v4+)

Published: 2025-12-27
Author: DP
Views: 20
Category: Markdown
Content
## The Problem When using `marked.js` to convert Markdown to HTML, a common requirement is to add custom CSS classes to specific elements, such as tables, for styling. However, many developers using `marked.js` v4.0 and later follow older tutorials, attempting to override the `renderer.table` method, only to find the output is not the expected HTML but a confusing `[object Object]`. This article explains the reason behind this issue and provides the correct solution for modern versions of `marked.js`. --- ## The Root Cause: API Evolution The core reason for the `[object Object]` error is a major **breaking change** to the Renderer API in `marked.js` version 4.0. * **Old API (v3.x and below)**: Renderer functions (like `renderer.table`) received **partially rendered HTML strings** as arguments (e.g., `header` and `body` strings). The code typically looked like this: ```javascript // Old API, no longer applicable renderer.table = function (header, body) { return `<table class="my-table">...${header}${body}...</table>`; }; ``` * **New API (v4.x and above)**: Renderer functions now receive an **unprocessed Token object**. This object contains all the raw structural information of the Markdown element (like `header` and `rows` arrays), but its content still needs to be parsed. Treating this object directly as a string naturally results in `[object Object]`. --- ## The Solution: Custom Rendering with Token Objects For `marked.js` v4+, the correct approach is to register a custom renderer using the `marked.use()` method. Inside this renderer, you process the incoming `token` object to manually build the HTML structure. This method provides unprecedented flexibility and control. Here is a complete and verified code example, provided by DP@lib00: ```javascript // Import marked import { marked } from 'marked'; // Use marked.use() to apply a custom renderer marked.use({ // You can set other marked options here, e.g., gfm gfm: true, // Define a renderer object renderer: { /** * Custom table renderer * @param {object} token The incoming table token object * @param {Array<object>} token.header Array of header cell objects * @param {Array<Array<object>>} token.rows Array of table row arrays */ table(token) { // 1. Generate the table header (<thead>) HTML let headerHtml = ''; if (token.header.length > 0) { const headerCells = token.header .map(cell => { // CRITICAL: Use this.parser.parseInline() to render cell content return `<th>${this.parser.parseInline(cell.tokens)}</th>`; }) .join(''); headerHtml = `<thead><tr>${headerCells}</tr></thead>`; } // 2. Generate the table body (<tbody>) HTML let bodyHtml = ''; if (token.rows.length > 0) { const bodyRows = token.rows .map(row => { const rowCells = row .map(cell => { return `<td>${this.parser.parseInline(cell.tokens)}</td>`; }) .join(''); return `<tr>${rowCells}</tr>`; }) .join(''); bodyHtml = `<tbody>${bodyRows}</tbody>`; } // 3. Combine and return the final table HTML with a custom class const customClassName = 'table wiki-lib00-table'; // Your desired class return `<div class="table-container"><table class="${customClassName}">${headerHtml}${bodyHtml}</table></div>`; } } }); // --- Testing --- const markdownInput = ` | Project | Owner | Status | |------------|--------|------------| | wiki.lib00 | DP | **WIP** | | New Feature| Alex | *Pending* | `; // Note: marked.parse() is the recommended method in new versions const htmlOutput = marked.parse(markdownInput); console.log(htmlOutput); ``` ### Code Breakdown 1. **`marked.use({ renderer: { ... } })`**: This is the preferred way to register extensions or configurations with `marked`, making your setup more modular. 2. **`table(token)`**: Our custom `table` function receives a single `token` object as its argument. 3. **`token.header` and `token.rows`**: The `token` object contains properties like `header` (a 1D array for the table head) and `rows` (a 2D array for the table body). 4. **`this.parser.parseInline(cell.tokens)`**: This is the **most crucial part** of the solution. The content of each cell is no longer a simple string but an array of `tokens`. We must call `this.parser.parseInline()` to correctly render these tokens into HTML. This ensures that inline Markdown formatting within cells (like bold `**`, italics `*`) is parsed correctly. --- ## Conclusion By understanding the new API of `marked.js` v4+, we can easily achieve deep customization of Markdown elements. The key is to shift from a string-processing mindset to a token-object-processing one, using the tools provided by `this.parser` to finalize content rendering. While this new model requires a bit more code, it opens the door to much greater flexibility for developers. For more technical articles, visit wiki.lib00.com.
Related Contents