Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions docs/EXTENSION_TYPES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Extension Types in Marked.js

This document clarifies the different types of extensions in marked.js to help developers understand the API better.

## Two Types of Extensions

Marked.js has two distinct concepts that are both called "extensions" in the documentation, which can cause confusion:

### 1. MarkedExtensions (Configuration Objects)

**Type**: `MarkedExtension` interface
**Usage**: Passed to `marked.use()` or `new Marked()`
**Purpose**: Configuration objects that can contain various options and settings

```javascript
// MarkedExtension examples
marked.use({
gfm: true,
breaks: false,
pedantic: false
});

marked.use({
renderer: {
heading(text, level) {
return `<h${level}>${text}</h${level}>`;
}
}
});

marked.use({
extensions: [/* SyntaxExtensions go here */]
});
```

### 2. SyntaxExtensions (Custom Parsing Logic)

**Type**: `SyntaxExtension` interface
**Usage**: Objects inside the `extensions` array of a MarkedExtension
**Purpose**: Define custom tokenizers and renderers for new syntax

```javascript
// SyntaxExtension example
const customExtension = {
name: 'customBlock',
level: 'block',
start: (src) => src.match(/:::/)?.index,
tokenizer(src) {
const match = src.match(/^:::\s*(\w+)\s*\n([\s\S]*?)\n:::/);
if (match) {
return {
type: 'customBlock',
raw: match[0],
name: match[1],
content: match[2]
};
}
},
renderer(token) {
return `<div class="custom-${token.name}">${token.content}</div>`;
}
};

// Using the SyntaxExtension
marked.use({
extensions: [customExtension] // This is a MarkedExtension with SyntaxExtensions
});
```

## API Signatures Clarified

### 1. `new Marked(extension, extension, extension)`
- Each `extension` parameter is a **MarkedExtension**
- Can contain any MarkedExtension options including `extensions` array

### 2. `marked.use(extension)`
- The `extension` parameter is a **MarkedExtension**
- Can contain any MarkedExtension options including `extensions` array

### 3. `marked.use({ extensions: [SyntaxExtensions] })`
- The outer object is a **MarkedExtension**
- The `extensions` property contains an array of **SyntaxExtensions**

## Clear Terminology

To avoid confusion, we recommend using these prefixes in documentation:

- **MarkedExtension**: Configuration objects for `marked.use()`
- **SyntaxExtension**: Custom parsing logic objects for the `extensions` array
- **Extension Options**: The `extensions` property within a MarkedExtension

## Examples of Clear Usage

```javascript
// MarkedExtension with various options
const markdownConfig = {
gfm: true,
breaks: false,
renderer: {
// custom renderer methods
}
};

// MarkedExtension with SyntaxExtensions
const customSyntaxConfig = {
extensions: [
{
name: 'alert',
level: 'block',
start: (src) => src.match(/^!!!\s/)?.index,
tokenizer(src) {
// custom tokenizer logic
},
renderer(token) {
// custom renderer logic
}
}
]
};

// Using both
marked.use(markdownConfig);
marked.use(customSyntaxConfig);
```

## Migration from Previous Versions

When upgrading from older versions of marked.js, you may need to separate your configuration:

```javascript
// Old way (confusing)
const config = {
gfm: true,
extensions: [customExtension1, customExtension2]
};

// New way (clear separation)
const markdownOptions = {
gfm: true
};

const customExtensions = {
extensions: [customExtension1, customExtension2]
};

marked.use(markdownOptions);
marked.use(customExtensions);
```
Comment on lines +126 to +148
Copy link
Member

@UziTech UziTech Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do encourage setting them together. Often an external extension will set options and add syntax extensions and we want them to be easy to use.

I think we should remove this section


This separation makes it clear which parts are general configuration and which are custom parsing extensions.
4 changes: 2 additions & 2 deletions docs/USING_ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ console.log(marked.parse(markdownString));
|smartypants (**removed**)|`boolean` |`false` |v0.2.9 |Removed in v8.0.0 use [`marked-smartypants`](https://www.npmjs.com/package/marked-smartypants) to use "smart" typographic punctuation for things like quotes and dashes.|
|xhtml (**removed**)|`boolean` |`false` |v0.3.2 |Removed in v8.0.0 use [`marked-xhtml`](https://www.npmjs.com/package/marked-xhtml) to emit self-closing HTML tags for void elements (&lt;br/&gt;, &lt;img/&gt;, etc.) with a "/" as required by XHTML.|

<h2 id="extensions">Known Extensions</h2>
<h2 id="extensions">Known MarkedExtensions</h2>

Marked can be extended using [custom extensions](/using_pro#extensions). This is a list of extensions that can be used with `marked.use(extension)`.
Marked can be extended using [custom SyntaxExtensions](/using_pro#extensions). This is a list of **MarkedExtension** packages that can be used with `marked.use(extension)`.

<!-- Keep this list ordered alphabetically by name -->

Expand Down
65 changes: 56 additions & 9 deletions docs/USING_PRO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

To champion the single-responsibility and open/closed principles, we have tried to make it relatively painless to extend Marked. If you are looking to add custom functionality, this is the place to start.

> **📖 Extension Types**: Marked.js has two different types of "extensions" that can be confusing. See [Extension Types Documentation](/extension_types) for a clear explanation of **MarkedExtensions** vs **SyntaxExtensions**.

<h2 id="use">marked.use()</h2>

`marked.use(extension)` is the recommended way to extend Marked. The `extension` object can contain any [option](/using_advanced#options) available in Marked:
`marked.use(extension)` is the recommended way to extend Marked. The `extension` parameter is a **MarkedExtension** object that can contain any [option](/using_advanced#options) available in Marked:


```js
Expand All @@ -17,16 +19,16 @@ marked.use({
});
```

You can also supply multiple `extension` objects at once.
You can also supply multiple **MarkedExtension** objects at once.

```js
marked.use(myExtension, extension2, extension3);
marked.use(myMarkedExtension, markedExtension2, markedExtension3);

\\ EQUIVALENT TO:

marked.use(myExtension);
marked.use(extension2);
marked.use(extension3);
marked.use(myMarkedExtension);
marked.use(markedExtension2);
marked.use(markedExtension3);
```

All options will overwrite those previously set, except for the following options which will be merged with the existing framework and can be used to change or extend the functionality of Marked: `renderer`, `tokenizer`, `hooks`, `walkTokens`, and `extensions`.
Expand All @@ -35,7 +37,7 @@ All options will overwrite those previously set, except for the following option

* The `walkTokens` option is a function that will be called to post-process every token before rendering.

* The `extensions` option is an array of objects that can contain additional custom `renderer` and `tokenizer` steps that will execute before any of the default parsing logic occurs.
* The `extensions` option is an array of **SyntaxExtension** objects that can contain additional custom `renderer` and `tokenizer` steps that will execute before any of the default parsing logic occurs.

Importantly, ensure that the extensions are only added to `marked` once (ie in the global scope of a regular JavaScript or TypeScript module). If they are added in a function that is called repeatedly, or in the JS for an HTML component in a library such as Svelte, your extensions will be added repeatedly, eventually causing a recursion error. If you cannot prevent the code from being run repeatedly, you should create a [Marked instance](/using_advanced#instance) so that your extensions are stored independently from the global instance Marked provides.

Expand Down Expand Up @@ -392,9 +394,54 @@ console.log(marked.parse(`_The formula is $a_ b=c_ d$._`));

***

<h2 id="extensions">Custom Extensions : <code>extensions</code></h2>
<h2 id="extension-types-example">Extension Types Example</h2>

Here's a clear example showing the difference between **MarkedExtensions** and **SyntaxExtensions**:

```js
// 1. MarkedExtension - Configuration object passed to marked.use()
const markdownConfig = {
gfm: true,
breaks: false,
pedantic: false
};

// 2. SyntaxExtension - Custom parsing logic
const customBlockExtension = {
name: 'customBlock',
level: 'block',
start: (src) => src.match(/:::/)?.index,
tokenizer(src) {
const match = src.match(/^:::\s*(\w+)\s*\n([\s\S]*?)\n:::/);
if (match) {
return {
type: 'customBlock',
raw: match[0],
name: match[1],
content: match[2]
};
}
},
renderer(token) {
return `<div class="custom-${token.name}">${token.content}</div>`;
}
};

// 3. MarkedExtension containing SyntaxExtensions
const customSyntaxConfig = {
extensions: [customBlockExtension] // SyntaxExtensions go here
};

// Usage
marked.use(markdownConfig); // MarkedExtension
marked.use(customSyntaxConfig); // MarkedExtension with SyntaxExtensions
```

***

<h2 id="extensions">Custom SyntaxExtensions : <code>extensions</code></h2>

You may supply an `extensions` array to the `options` object. This array can contain any number of `extension` objects, using the following properties:
You may supply an `extensions` array to the `options` object. This array can contain any number of **SyntaxExtension** objects, using the following properties:

<dl>
<dt><code><strong>name</strong></code></dt>
Expand Down
1 change: 1 addition & 0 deletions docs/_document.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ <h1>Marked Documentation</h1>
<li><a href="/using_pro#walk-tokens">Walk Tokens</a></li>
<li><a href="/using_pro#hooks">Hooks</a></li>
<li><a href="/using_pro#extensions">Custom Extensions</a></li>
<li><a href="/extension_types">Extension Types</a></li>
<li><a href="/using_pro#async">Async Marked</a></li>
<li><a href="/using_pro#lexer">Lexer</a></li>
<li><a href="/using_pro#parser">Parser</a></li>
Expand Down
4 changes: 2 additions & 2 deletions src/MarkedOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface RendererExtension<ParserOutput = string, RendererOutput = strin
renderer: RendererExtensionFunction<ParserOutput, RendererOutput>;
}

export type TokenizerAndRendererExtension<ParserOutput = string, RendererOutput = string> = TokenizerExtension | RendererExtension<ParserOutput, RendererOutput> | (TokenizerExtension & RendererExtension<ParserOutput, RendererOutput>);
export type SyntaxExtension<ParserOutput = string, RendererOutput = string> = TokenizerExtension | RendererExtension<ParserOutput, RendererOutput> | (TokenizerExtension & RendererExtension<ParserOutput, RendererOutput>);

type HooksApi<ParserOutput = string, RendererOutput = string> = Omit<_Hooks<ParserOutput, RendererOutput>, 'constructor' | 'options' | 'block'>;
type HooksObject<ParserOutput = string, RendererOutput = string> = {
Expand Down Expand Up @@ -64,7 +64,7 @@ export interface MarkedExtension<ParserOutput = string, RendererOutput = string>
* Add tokenizers and renderers to marked
*/
extensions?:
| TokenizerAndRendererExtension<ParserOutput, RendererOutput>[]
| SyntaxExtension<ParserOutput, RendererOutput>[]
| null;

/**
Expand Down
6 changes: 3 additions & 3 deletions test/types/marked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { marked } from 'marked';
// other exports

import { Lexer, Parser, Tokenizer, Renderer, TextRenderer, Marked } from 'marked';
import type { Tokens, MarkedExtension, TokenizerAndRendererExtension, Token ,TokenizerExtension, MarkedOptions, TokensList, RendererExtension, RendererObject } from 'marked';
import type { Tokens, MarkedExtension, SyntaxExtension, Token ,TokenizerExtension, MarkedOptions, TokensList, RendererExtension, RendererObject } from 'marked';

const tokenizer = new marked.Tokenizer();

Expand Down Expand Up @@ -215,7 +215,7 @@ const rendererExtension: RendererExtension = {
}
};

const tokenizerAndRendererExtension: TokenizerAndRendererExtension = {
const syntaxExtension: SyntaxExtension = {
name: 'name',
level: 'block',
tokenizer(src: string) {
Expand All @@ -237,7 +237,7 @@ const tokenizerAndRendererExtension: TokenizerAndRendererExtension = {
};

marked.use({
extensions: [tokenizerExtension, rendererExtension, tokenizerAndRendererExtension]
extensions: [tokenizerExtension, rendererExtension, syntaxExtension]
});

const asyncExtension: MarkedExtension = {
Expand Down