Skip to content

Create an extension

A Showdown extension is a function that returns an array of lang, output or listener sub-extensions (henceforth called "sub-extensions").

var myext = function () {
  var myext1 = {
    type: 'lang',
    regex: /markdown/g,
    replace: 'showdown'
  };
  var myext2 = {
    /* extension code */
  };
  return [myext1, myext2];
}

Each sub-extension (myext1 and myext2 in the example above) should be an object that defines the behavior of the corresponding sub-extension.

Sub-extension object properties

A sub-extension object should have a type property that defines the type of the sub-extension, plus:

Type

Type is a required property that defines the nature of the corresponding sub-extensions. It takes one of three values:

  • lang: language extension to add new Markdown syntax to Showdown.

    lang extensions have the highest priority in the subparser order, so they are called after escaping and normalizing the input text and before calling any other subparser (or extension).

    When to use lang type

    For example, if you want the ^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0 syntax to automatically be rendered as an embedded YouTube video.

    Deprecated

    lang extensions are now sugar over the event system: each one is registered as a listener on the makehtml.onPreParse event. They are deprecated and loading one logs a deprecation warning — prefer writing a listener extension on makehtml.onPreParse (or makehtml.onStart if you need the raw, pre-escape source) directly.

  • output: output extension (or modifier) to alter the HTML output generated by Showdown.

    output extensions have the lowest priority in the subparser order, so they are called right before the cleanup step and after calling all other subparsers.

    When to use output type

    For example, if you want the <div class="header"> to become <header>.

    Deprecated

    output extensions are now sugar over the event system: each one is registered as a listener on the makehtml.onEnd event. They are deprecated and loading one logs a deprecation warning — prefer writing a listener extension on makehtml.onEnd directly.

  • listener: listener extension to hook the event system and inspect or modify a sub-parser's captures, matches, attributes, or output mid-conversion.

    Unlike lang/output extensions, a listener extension does not use regex/replace or filter. Instead, it defines a listeners object. See Listener extensions below.

    When to use listener type

    For example, if you want to add a class attribute to every blockquote, or rewrite the captured text of a specific construct without re-implementing its sub-parser.

Regex and replace

regex/replace properties are similar to the Javascript's string.replace function and work the same way:

  • regex: a string or a RegExp object.

    If regex is a string, it will automatically be assigned a g (global) modifier, that is, all matches of that string will be replaced.

  • replace a string or a function.

    If replace is a string, you can use the $1 syntax for group substitution, exactly as if it were making use of string.replace.

Regex and replace example

In this example, all the occurrences of markdown will be replaced with showndown.

var myext = {
  type: 'lang',
  regex: /markdown/g,
  replace: 'showdown'
};

Filter

Alternately, if you'd like to have more control over the modification process, you can use filter property.

This property should be used as a function that acts as a callback. The callback should receive the following parameters:

  1. text: the source text within the Showdown's engine.
  2. converter: the full instance of the current Showdown's converter object.
  3. options: the options used to initialize the converter

The filter function should return the transformed text. If it returns nothing, your transformation is silently skipped and the text passes through unchanged.

lang/output extensions now run as listeners on the document-level makehtml.onPreParse / makehtml.onEnd events, so the options passed to filter is a read-only copy of the converter's options. Reading it works as before; mutating it has no effect on the conversion (mutating options was never supported anyway).

Filter example

var myext = {
  type: 'lang',
  filter: function (text, converter, options) {
    // ... do stuff to text ...
    return text;
  }
};

Use filter with care

Although Filter extensions are more powerful, they have a few pitfalls that you should keep in mind before using them, especially regarding the converter parameter.

Since the converter parameter passed to the filter function is the fully initialized instance, any change made to it will be propagated outside the scope of the filter function and will remain there until a new converter instance is created. So, it is not recommended to make ANY change to the converter object.

Another aspect is that if you call the converter recursively, it will call your extension itself at some point. It may lead to infinite recursion in some circumstances, and it's up to you to prevent this. A simple solution is to place a kind of safeguard to disable your extension if it's called more than x times:

var x = 0;
var myext = {
  type: 'lang',
  filter: function (text, converter) {
    if (x < 3) {
      ++x;
      someSubText = converter.makeHtml(someSubText);
    }
  } 
};

Listener extensions

A listener sub-extension hooks the event system. Instead of regex/replace or filter, it defines a listeners property: an object that maps event names to callback functions.

showdown.extension('addBlockquoteClass', function () {
  return [{
    type: 'listener',
    listeners: {
      'makehtml.blockquote.onCapture': function (evt) {
        // add a class attribute to every blockquote
        evt.attributes['class'] = 'fancy-quote';
        return evt;
      }
    }
  }];
});

var converter = new showdown.Converter({ extensions: ['addBlockquoteClass'] });

Each callback receives a showdown.Event object and must return it (after any modifications). What you can read or change depends on the event — see the event types and their property tables.

The listeners property is required for listener extensions and must be a hash of [event name]: [callback function]. An invalid shape throws on registration.

converter.listen() vs a listener extension

A listener extension is the reusable, registrable form. If you only need to attach a one-off handler to a single converter, you can call converter.listen(eventName, callback) directly instead.

Register an extension

To let Showdown know what extensions are available, you need to register them in the Showdown global object.

To register an extension, call the showdown.extension function with two parameters: the first one is the extension name; the second one is the actual extension.

showdown.extension('myext', myext);

Test an extension

An extension is tested the same way as any other Showdown behavior: instantiate a Converter configured with the extension and assert on the output of makeHtml().

var showdown = require('showdown');

showdown.extension('myext', myExtension);
var converter = new showdown.Converter({ extensions: ['myext'] });

converter.makeHtml('markdown') === '<p>showdown</p>'; // assert this

The project's own behavioral tests are data-driven fixture pairs: for every <name>.md file there is a sibling <name>.html with the expected output (see the suites under test/functional/makehtml/). If you contribute an extension to Showdown, follow that pattern by adding a fixture pair and wiring up a converter configured with your extension.

Additional information

Escape and normalization

Showdown performs the following escape/normalization:

  • Replaces ¨ (trema) with ¨T
  • Replaces $ (dollar sign) with ¨D
  • Normalizes line endings (\r, \r\n are converted into \n)
  • Uses \r as a char placeholder

This only applies to language extensions since these chars are unescaped before output extensions are run.

Keep in mind that these modifications happen before language extensions are run, so if your extension relies on any of those chars, you have to make the appropriate adjustments.

Implementation concerns

One of the concerns is maintaining both client-side and server-side compatibility. You can do this with a few lines of boilerplate code.:

(function (extension) {
  if (typeof showdown !== 'undefined') {
    // global (browser or node.js global)
    extension(showdown);
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define(['showdown'], extension);
  } else if (typeof exports === 'object') {
    // Node, CommonJS-like
    module.exports = extension(require('showdown'));
  } else {
    // showdown was not found so an error is thrown
    throw Error('Could not find showdown library');
  }
}(function (showdown) {
  // loading extension into showdown
  showdown.extension('myext', function () {
    var myext = { /* ... actual extension code ... */ };
    return [myext];
  });
}));

In the code above, the extension definition is wrapped in a self-executing function to prevent pollution of the global scope. It has another benefit of creating several scope layers that can be useful for interaction between sub-extensions global-wise or local-wise.

It is also loaded conditionally to make it compatible with different loading mechanisms (such as browser, CommonJS, or AMD).