A Markdown to HTML converter written in Javascript
—
Plugin architecture for adding custom parsing behavior and output modifications to Showdown.
Registers or retrieves extensions globally.
/**
* Gets or registers an extension
* @param name - Extension name
* @param ext - Extension definition, array of extensions, or factory function
* @returns Extension array when getting, void when setting
*/
showdown.extension(name: string, ext?: Extension | Extension[] | Function): Extension[] | voidUsage Examples:
// Register a simple extension
showdown.extension('highlight', {
type: 'output',
regex: /<pre><code\s+class="([^"]+)">([\s\S]*?)<\/code><\/pre>/g,
replace: '<pre class="$1"><code>$2</code></pre>'
});
// Register extension from function
showdown.extension('custom', function() {
return {
type: 'lang',
filter: function(text) {
return text.replace(/\[TODO\]/g, '<span class="todo">TODO</span>');
}
};
});
// Get registered extension
const highlightExt = showdown.extension('highlight');
console.log(highlightExt);Gets all registered extensions.
/**
* Gets all extensions registered
* @returns Object with extension names as keys and extension arrays as values
*/
showdown.getAllExtensions(): { [key: string]: Extension[] }Usage Examples:
// Get all registered extensions
const allExtensions = showdown.getAllExtensions();
console.log('Available extensions:', Object.keys(allExtensions));
// Check if specific extension exists
const extensions = showdown.getAllExtensions();
if ('highlight' in extensions) {
console.log('Highlight extension is available');
}Removes a registered extension.
/**
* Remove an extension
* @param name - Extension name to remove
*/
showdown.removeExtension(name: string): voidUsage Examples:
// Register an extension
showdown.extension('temp', {
type: 'lang',
filter: text => text
});
// Remove it
showdown.removeExtension('temp');
// Verify removal
const extensions = showdown.getAllExtensions();
console.log('temp' in extensions); // falseRemoves all registered extensions.
/**
* Removes all extensions
*/
showdown.resetExtensions(): voidUsage Examples:
// Register several extensions
showdown.extension('ext1', { type: 'lang', filter: text => text });
showdown.extension('ext2', { type: 'output', filter: text => text });
// Clear all extensions
showdown.resetExtensions();
// Verify cleanup
const extensions = showdown.getAllExtensions();
console.log(Object.keys(extensions).length); // 0Validates extension format and structure.
/**
* Validate extension format
* @param ext - Extension object to validate
* @returns True if valid, false otherwise
*/
showdown.validateExtension(ext: Extension): booleanUsage Examples:
// Valid extension
const validExt = {
type: 'lang',
filter: function(text) { return text; }
};
console.log(showdown.validateExtension(validExt)); // true
// Invalid extension (missing filter/regex)
const invalidExt = {
type: 'lang'
};
console.log(showdown.validateExtension(invalidExt)); // falseModify the Markdown parsing process before HTML generation.
interface LanguageExtension {
type: 'lang';
filter?: (text: string, converter: showdown.Converter, options: ConverterOptions) => string;
regex?: RegExp | string;
replace?: string | Function;
}Usage Examples:
// Filter-based language extension
showdown.extension('alerts', {
type: 'lang',
filter: function(text) {
return text.replace(/\[!(INFO|WARNING|ERROR)\]\s*(.+)/g, function(match, type, content) {
return `<div class="alert alert-${type.toLowerCase()}">${content}</div>`;
});
}
});
// Regex-based language extension
showdown.extension('mentions', {
type: 'lang',
regex: /@([a-zA-Z0-9_]+)/g,
replace: '<a href="/users/$1">@$1</a>'
});Modify the HTML output after Markdown processing.
interface OutputExtension {
type: 'output';
filter?: (html: string, converter: showdown.Converter, options: ConverterOptions) => string;
regex?: RegExp | string;
replace?: string | Function;
}Usage Examples:
// Add syntax highlighting classes
showdown.extension('syntax-highlight', {
type: 'output',
regex: /<pre><code\s+class="language-([^"]+)">/g,
replace: '<pre><code class="language-$1 hljs">'
});
// Process all links
showdown.extension('external-links', {
type: 'output',
filter: function(html) {
return html.replace(/<a href="([^"]*)">/g, function(match, url) {
if (url.startsWith('http')) {
return `<a href="${url}" target="_blank" rel="noopener">`;
}
return match;
});
}
});Event-based extensions that respond to conversion events.
interface ListenerExtension {
type: 'listener';
listeners: {
[eventName: string]: (
evtName: string,
text: string,
converter: showdown.Converter,
options: ConverterOptions,
globals: any
) => string | void;
};
}Usage Examples:
showdown.extension('stats', {
type: 'listener',
listeners: {
'conversion.start': function(evtName, text, converter) {
console.log('Starting conversion of', text.length, 'characters');
},
'conversion.end': function(evtName, html, converter) {
console.log('Generated', html.length, 'characters of HTML');
}
}
});// Load extension from file (Node.js)
const myExtension = require('./my-extension');
showdown.extension('my-ext', myExtension);
// Use in converter
const converter = new showdown.Converter({
extensions: ['my-ext']
});// Define and register inline
showdown.extension('custom', {
type: 'lang',
regex: /\[\[([^\]]+)\]\]/g,
replace: '<span class="wiki-link">$1</span>'
});// Extension factory
showdown.extension('configurable', function(options) {
options = options || {};
const prefix = options.prefix || 'default';
return {
type: 'lang',
filter: function(text) {
return text.replace(/\[CUSTOM\]/g, `[${prefix}]`);
}
};
});// Register globally
showdown.extension('highlight', highlightExtension);
// All converters can use it
const converter1 = new showdown.Converter({ extensions: ['highlight'] });
const converter2 = new showdown.Converter({ extensions: ['highlight'] });// Load extension only for specific converter
const converter = new showdown.Converter();
converter.addExtension(highlightExtension, 'highlight');// Load multiple extensions
const converter = new showdown.Converter({
extensions: ['highlight', 'mentions', 'alerts']
});
// Extension order matters - earlier extensions process first// Good: Defensive programming
showdown.extension('safe-ext', {
type: 'lang',
filter: function(text, converter, options) {
if (!text || typeof text !== 'string') {
return text;
}
// Process text safely
try {
return text.replace(/pattern/g, 'replacement');
} catch (error) {
console.error('Extension error:', error);
return text;
}
}
});
// Good: Respect options
showdown.extension('conditional', {
type: 'lang',
filter: function(text, converter, options) {
if (!options.customFeature) {
return text;
}
return processText(text);
}
});// Extension with error handling
showdown.extension('robust', {
type: 'lang',
filter: function(text) {
try {
return complexProcessing(text);
} catch (error) {
console.warn('Extension failed, returning original text:', error);
return text;
}
}
});Advanced method for registering or retrieving individual parsing components.
/**
* Get or set a subParser
* @param name - SubParser name
* @param func - SubParser function (optional, for registration)
* @returns SubParser function when getting, void when setting
*/
showdown.subParser(name: string, func?: Function): Function | voidUsage Examples:
// Register a custom subParser
showdown.subParser('customBlock', function(text, options, globals) {
return text.replace(/\[CUSTOM\]/g, '<div class="custom">Custom Block</div>');
});
// Get existing subParser
const headerParser = showdown.subParser('headers');
console.log(typeof headerParser); // 'function'
// Use in extension
showdown.extension('enhanced', {
type: 'lang',
filter: function(text, converter, options) {
// Use registered subParser
const customParser = showdown.subParser('customBlock');
return customParser(text, options, {});
}
});Note: SubParsers are internal parsing components. Modifying them can affect core functionality and should be done with caution.
Install with Tessl CLI
npx tessl i tessl/npm-showdown