{"id":13445533,"url":"https://github.com/ariabuckles/simple-markdown","last_synced_at":"2026-01-15T22:20:37.676Z","repository":{"id":22208533,"uuid":"25541148","full_name":"ariabuckles/simple-markdown","owner":"ariabuckles","description":"JavaScript markdown parsing, made simple","archived":false,"fork":false,"pushed_at":"2023-03-01T21:48:49.000Z","size":1803,"stargazers_count":524,"open_issues_count":37,"forks_count":99,"subscribers_count":63,"default_branch":"master","last_synced_at":"2026-01-14T11:28:14.372Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"FortAwesome/Font-Awesome","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ariabuckles.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2014-10-21T19:18:38.000Z","updated_at":"2026-01-10T12:41:16.000Z","dependencies_parsed_at":"2024-02-04T17:38:38.217Z","dependency_job_id":null,"html_url":"https://github.com/ariabuckles/simple-markdown","commit_stats":{"total_commits":214,"total_committers":32,"mean_commits":6.6875,"dds":0.7897196261682243,"last_synced_commit":"7fb8bb5943ee4e561fec17c2e271a327f4e86d64"},"previous_names":["khan/simple-markdown"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/ariabuckles/simple-markdown","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ariabuckles%2Fsimple-markdown","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ariabuckles%2Fsimple-markdown/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ariabuckles%2Fsimple-markdown/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ariabuckles%2Fsimple-markdown/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ariabuckles","download_url":"https://codeload.github.com/ariabuckles/simple-markdown/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ariabuckles%2Fsimple-markdown/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28472626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T22:13:38.078Z","status":"ssl_error","status_checked_at":"2026-01-15T22:12:11.737Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T05:00:35.632Z","updated_at":"2026-01-15T22:20:37.659Z","avatar_url":"https://github.com/ariabuckles.png","language":"JavaScript","funding_links":[],"categories":["UI components"],"sub_categories":["Markdown"],"readme":"🚚 _**As of April 2022 this repo is no longer the home of `simple-markdown`. The contents and development activity have moved into the Perseus repo [here](https://github.com/Khan/perseus/tree/main/packages/simple-markdown).**_\n\n# simple-markdown\n\nsimple-markdown is a markdown-like parser designed for simplicity\nand extensibility.\n\n[Change log](https://github.com/Khan/simple-markdown/releases)\n\n## Philosophy\n\nMost markdown-like parsers aim for [speed][marked] or\n[edge case handling][commonmark].\nsimple-markdown aims for extensibility and simplicity.\n\n[marked]: https://github.com/chjj/marked\n[commonmark]: https://github.com/jgm/CommonMark\n\nWhat does this mean?\nMany websites using markdown-like languages have custom extensions,\nsuch as `@`mentions or issue number linking. Unfortunately, most\nmarkdown-like parsers don't allow extension without\nforking, and can be difficult to modify even when forked.\nsimple-markdown is designed to allow simple addition of\ncustom extensions without needing to be forked.\n\nAt Khan Academy, we use simple-markdown to format\nover half of our math exercises, because we need\n[markdown extensions][perseusmarkdown] for math text and\ninteractive widgets.\n\n[perseusmarkdown]: https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx\n\nsimple-markdown is [MIT licensed][license].\n\n[license]: https://github.com/Khan/simple-markdown/blob/master/LICENSE\n\n## Getting started\n\nFirst, let's parse and output some generic markdown using\nsimple-markdown.\n\nIf you want to run these examples in\nnode, you should run `npm install` in the simple-markdown\nfolder or `npm install simple-markdown` in your project's\nfolder. Then you can acquire the `SimpleMarkdown` variable\nwith:\n\n```javascript\nvar SimpleMarkdown = require(\"simple-markdown\");\n```\n\nThen let's get a basic markdown parser and outputter.\n`SimpleMarkdown` provides default parsers/outputters for\ngeneric markdown:\n\n```javascript\nvar mdParse = SimpleMarkdown.defaultBlockParse;\nvar mdOutput = SimpleMarkdown.defaultOutput;\n```\n\n`mdParse` can give us a syntax tree:\n\n```javascript\nvar syntaxTree = mdParse(\"Here is a paragraph and an *em tag*.\");\n```\n\nLet's inspect our syntax tree:\n\n```javascript\n    // pretty-print this with 4-space indentation:\n    console.log(JSON.stringify(syntaxTree, null, 4));\n    =\u003e [\n        {\n            \"content\": [\n                {\n                    \"content\": \"Here is a paragraph and an \",\n                    \"type\": \"text\"\n                },\n                {\n                    \"content\": [\n                        {\n                            \"content\": \"em tag\",\n                            \"type\": \"text\"\n                        }\n                    ],\n                    \"type\": \"em\"\n                },\n                {\n                    \"content\": \".\",\n                    \"type\": \"text\"\n                }\n            ],\n            \"type\": \"paragraph\"\n        }\n    ]\n```\n\nThen to turn that into an array of React elements, we can\ncall `mdOutput`:\n\n```javascript\n    mdOutput(syntaxTree)\n    =\u003e [ { type: 'div',\n        key: null,\n        ref: null,\n        _owner: null,\n        _context: {},\n        _store: { validated: false, props: [Object] } } ]\n```\n\n## Adding a simple extension\n\nLet's add an underline extension! To do this, we'll need to create\na new rule and then make a new parser/outputter. The next section\nwill explain how all of these steps work in greater detail. (To\nfollow along with these examples, you'll also need\n[underscore][underscore].)\n\n[underscore]: http://underscorejs.org/\n\nFirst, we create a new rule. We'll look for double underscores\nsurrounding text.\n\nWe'll put underlines right\nbefore `em`s, so that `__` will be parsed before `_`\nfor emphasis/italics.\n\nA regex to capture this would look something\nlike `/^__([\\s\\S]+?)__(?!_)/`. This matches `__`, followed by\nany content until it finds another `__` not followed by a\nthird `_`.\n\n```javascript\nvar underlineRule = {\n  // Specify the order in which this rule is to be run\n  order: SimpleMarkdown.defaultRules.em.order - 0.5,\n\n  // First we check whether a string matches\n  match: function (source) {\n    return /^__([\\s\\S]+?)__(?!_)/.exec(source);\n  },\n\n  // Then parse this string into a syntax node\n  parse: function (capture, parse, state) {\n    return {\n      content: parse(capture[1], state),\n    };\n  },\n\n  // Finally transform this syntax node into a\n  // React element\n  react: function (node, output) {\n    return React.DOM.u(null, output(node.content));\n  },\n\n  // Or an html element:\n  // (Note: you may only need to make one of `react:` or\n  // `html:`, as long as you never ask for an outputter\n  // for the other type.)\n  html: function (node, output) {\n    return \"\u003cu\u003e\" + output(node.content) + \"\u003c/u\u003e\";\n  },\n};\n```\n\nThen, we need to add this rule to the other rules:\n\n```javascript\nvar rules = _.extend({}, SimpleMarkdown.defaultRules, {\n  underline: underlineRule,\n});\n```\n\nFinally, we need to build our parser and outputters:\n\n```javascript\nvar rawBuiltParser = SimpleMarkdown.parserFor(rules);\nvar parse = function (source) {\n  var blockSource = source + \"\\n\\n\";\n  return rawBuiltParser(blockSource, { inline: false });\n};\n// You probably only need one of these: choose depending on\n// whether you want react nodes or an html string:\nvar reactOutput = SimpleMarkdown.outputFor(rules, \"react\");\nvar htmlOutput = SimpleMarkdown.outputFor(rules, \"html\");\n```\n\nNow we can use our custom `parse` and `output` functions to parse\nmarkdown with underlines!\n\n```javascript\n    var syntaxTree = parse(\"__hello underlines__\");\n    console.log(JSON.stringify(syntaxTree, null, 4));\n    =\u003e [\n        {\n            \"content\": [\n                {\n                    \"content\": [\n                        {\n                            \"content\": \"hello underlines\",\n                            \"type\": \"text\"\n                        }\n                    ],\n                    \"type\": \"underline\"\n                }\n            ],\n            \"type\": \"paragraph\"\n        }\n    ]\n\n    reactOutput(syntaxTree)\n    =\u003e [ { type: 'div',\n        key: null,\n        ref: null,\n        _owner: null,\n        _context: {},\n        _store: { validated: false, props: [Object] } } ]\n\n    htmlOutput(syntaxTree)\n\n    =\u003e '\u003cdiv class=\"paragraph\"\u003e\u003cu\u003ehello underlines\u003c/u\u003e\u003c/div\u003e'\n```\n\n## Basic parsing/output API\n\n#### `SimpleMarkdown.defaultBlockParse(source)`\n\nReturns a syntax tree of the result of parsing `source` with the\ndefault markdown rules. Assumes a block scope.\n\n#### `SimpleMarkdown.defaultInlineParse(source)`\n\nReturns a syntax tree of the result of parsing `source` with the\ndefault markdown rules, where `source` is assumed to be inline text.\nDoes not emit `\u003cp\u003e` elements. Useful for allowing inline markdown\nformatting in one-line fields where paragraphs, lists, etc. are\ndisallowed.\n\n#### `SimpleMarkdown.defaultImplicitParse(source)`\n\nParses `source` as block if it ends with `\\n\\n`, or inline if not.\n\n#### `SimpleMarkdown.defaultOutput(syntaxTree)`\n\nReturns React-renderable output for `syntaxTree`.\n\n_Note: raw html output will be coming soon_\n\n## Extension Overview\n\nElements in simple-markdown are generally created from rules.\nFor parsing, rules must specify `match` and `parse` methods.\nFor output, rules must specify a `react` or `html` method\n(or both), depending on which outputter you create afterwards.\n\nHere is an example rule, a slightly modified version of what\nsimple-markdown uses for parsing **strong** (**bold**) text:\n\n```javascript\n    strong: {\n        match: function(source, state, lookbehind) {\n            return /^\\*\\*([\\s\\S]+?)\\*\\*(?!\\*)/.exec(source);\n        },\n        parse: function(capture, recurseParse, state) {\n            return {\n                content: recurseParse(capture[1], state)\n            };\n        },\n        react: function(node, recurseOutput) {\n            return React.DOM.strong(null, recurseOutput(node.content));\n        },\n        html: function(node, recurseOutput) {\n            return '\u003cstrong\u003e' + recurseOutput(node.content) + '\u003c/strong\u003e';\n        },\n    },\n```\n\nLet's look at those three methods in more detail.\n\n#### `match(source, state, lookbehind)`\n\nsimple-markdown calls your `match` function to determine whether the\nupcoming markdown source matches this rule or not.\n\n`source` is the upcoming source, beginning at the current position of\nparsing (source[0] is the next character).\n\n`state` is a mutable state object to allow for more complicated matching\nand parsing. The most common field on `state` is `inline`, which all of\nthe default rules set to true when we are in an inline scope, and false\nor undefined when we are in a block scope.\n\n**DEPRECATED - use `state.prevCapture` instead.** `lookbehind` is the string previously captured at this parsing level, to\nallow for lookbehind. For example, lists check that lookbehind ends with\n`/^$|\\n *$/` to ensure that lists only match at the beginning of a new\nline.\n\nIf this rule matches, `match` should return an object, array, or\narray-like object, which we'll call `capture`, where `capture[0]`\nis the full matched source, and any other fields can be used in the\nrule's `parse` function. The return value from `Regexp.prototype.exec`\nfits this requirement, and the common use case is to return the result\nof `someRegex.exec(source)`.\n\nIf this rule does not match, `match` should return null.\n\nNOTE: If you are using regexes in your match function, your regex\nshould always begin with `^`. Regexes without leading `^`s can\ncause unexpected output or infinite loops.\n\n#### `parse(capture, recurseParse, state)`\n\n`parse` takes the output of `match` and transforms it into a syntax\ntree node object, which we'll call `node` here.\n\n`capture` is the non-null result returned from match.\n\n`recurseParse` is a function that can be called on sub-content and\nstate to recursively parse the sub-content. This returns an array.\n\n`state` is the mutable state threading object, which can be examined\nor modified, and should be passed as the third argument to any\n`recurseParse` calls.\n\nFor example, to parse inline sub-content, you can add `inline: true`\nto state, or `inline: false` to force block parsing (to leave the\nparsing scope alone, you can just pass `state` with no modifications).\nFor example:\n\n```javascript\nvar innerText = capture[1];\nrecurseParse(\n  innerText,\n  _.defaults(\n    {\n      inline: true,\n    },\n    state\n  )\n);\n```\n\n`parse` should return a `node` object, which can have custom fields\nthat will be passed to `output`, below. The one reserved field is\n`type`, which designates the type of the node, which will be used\nfor output. If no type is specified, simple-markdown will use the\ncurrent rule's type (the common case). If you have multiple ways\nto parse a single element, it can be useful to have multiple rules\nthat all return nodes of the same type.\n\n#### `react(node, recurseOutput, state)`\n\n`react` takes a syntax tree `node` and transforms it into\nReact-renderable output.\n\n`node` is the return value from `parse`, which has a type\nfield of the same type as the current rule, as well as any\ncustom fields created by `parse`.\n\n`recurseOutput` is a function to recursively output sub-tree\nnodes created by using `recurseParse` in `parse`.\n\n`state` is the mutable state threading object, which can be\nexamined or modified, and should be passed as the second\nargument to any recurseOutput calls.\n\nThe simple-markdown API contains several helper methods for\ncreating rules, as well as methods for creating parsers and\noutputters from rules.\n\n## Extension API\n\nsimple-markdown includes access to the default list of rules,\nas well as several functions to allow you to create parsers and\noutputters from modifications of those default rules, or even\nfrom a totally custom rule list.\n\nThese functions are separated so that you can customize\nintermediate steps in the parsing/output process, if necessary.\n\n#### `SimpleMarkdown.defaultRules`\n\nThe default rules, specified as an object, where the keys are\nthe rule types, and the values are objects containing `order`,\n`match`, `parse`, `react`, and `html` fields (these rules can\nbe used for both parsing and outputting).\n\n#### `SimpleMarkdown.parserFor(rules)`\n\nTakes a `rules` object and returns a parser for the rule types\nin the rules object, in order of increasing `order` fields,\nwhich must be present and a finite number for each rule.\nIn the case of order field ties, rules are ordered\nlexicographically by rule name. Each of the rules in the `rules`\nobject must contain a `match` and a `parse` function.\n\n#### `SimpleMarkdown.outputFor(rules, key)`\n\nTakes a `rules` object and a `key` that indicates which key in\nthe rules object is mapped to the function that generates the\noutput type you want. This will be `'react'` or `'html'` unless\nyou are defining a custom output type.\n\nIt returns a function that outputs a single syntax tree node of\nany type that is in the `rules` object, given a node and a\nrecursive output function.\n\n#### Putting it all together\n\nGiven a set of rules, one can create a single function\nthat takes an input content string and outputs a\nReact-renderable as follows. Note that since many rules\nexpect blocks to end in `\"\\n\\n\"`, we append that to source\ninput manually, in addition to specifying `inline: false`\n(`inline: false` is technically optional for all of the\ndefault rules, which assume `inline` is false if it is\nundefined).\n\n```javascript\nvar rules = {\n    ...SimpleMarkdown.defaultRules,\n    paragraph: {\n        ...SimpleMarkdown.defaultRules.paragraph,\n        react: (node, output, state) =\u003e {\n            return \u003cp key={state.key}\u003e{output(node.content, state)}\u003c/p\u003e;\n        }\n    }\n};\n\nvar parser = SimpleMarkdown.parserFor(rules);\nvar reactOutput = SimpleMarkdown.outputFor(rules, 'react'));\nvar htmlOutput = SimpleMarkdown.outputFor(rules, 'html'));\n\nvar blockParseAndOutput = function(source) {\n    // Many rules require content to end in \\n\\n to be interpreted\n    // as a block.\n    var blockSource = source + \"\\n\\n\";\n    var parseTree = parser(blockSource, {inline: false});\n    var outputResult = htmlOutput(parseTree);\n    // Or for react output, use:\n    // var outputResult = reactOutput(parseTree);\n    return outputResult;\n};\n```\n\n## Extension rules helper functions\n\n_Coming soon_\n\n## LICENSE\n\nMIT. See the LICENSE file for text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fariabuckles%2Fsimple-markdown","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fariabuckles%2Fsimple-markdown","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fariabuckles%2Fsimple-markdown/lists"}