diff --git a/docs/en/topics/index.md b/docs/en/topics/index.md
index e6700518a..d192ed1d3 100644
--- a/docs/en/topics/index.md
+++ b/docs/en/topics/index.md
@@ -24,6 +24,7 @@ It is where most documentation should live, and is the natural "second step" aft
* [Page Types](page-types): What is a "page type" and how do you create one?
* [Search](search): Searching for properties in the database as well as other documents
* [Security](security): How to develop secure SilverStripe applications with good code examples
+ * [Shortcodes](shortcodes): Use simple placeholders for powerful content replacements like multimedia embeds
* [Templates](templates): SilverStripe template syntax: Variables, loops, includes and much more
* [Testing](testing): Functional and Unit Testing with PHPUnit and SilverStripe's testing framework
* [Developing Themes](theme-development): Package templates, images and CSS to a reusable theme
diff --git a/docs/en/topics/shortcodes.md b/docs/en/topics/shortcodes.md
new file mode 100644
index 000000000..d11b07091
--- /dev/null
+++ b/docs/en/topics/shortcodes.md
@@ -0,0 +1,143 @@
+# Shortcodes: Flexible Content Embedding
+
+## Overview
+
+The `[api:ShortcodeParser]` API is simple parser that allows you to map specifically
+formatted content to a callback to transform them into something else.
+You might know this concept from forum software which don't allow you to insert
+direct HTML, instead resorting to a custom syntax.
+
+In the CMS, authors often want to insert content elements which go beyond
+standard formatting, at an arbitrary position in their WYSIWYG editor.
+Shortcodes are a semi-technical solution for this. A good example would
+be embedding a 3D file viewer or a Google Map at a certain location.
+
+Here's some syntax variations:
+
+ [myshortcode]
+ [myshortcode /]
+ [myshortcode,myparameter="value"]
+ [myshortcode,myparameter="value"]Enclosed Content[/myshortcode]
+
+## Usage
+
+In its most basic form, you can invoke the `[api:ShortcodeParser]` directly:
+
+ :::php
+ ShortcodeParser::get_active()->parse($myvalue);
+
+In addition, shortcodes are automatically parsed on any database field which is declared
+as `[api:HTMLValue]` or `[api:HTMLText]`, when rendered into a template.
+This means you can use shortcodes on common fields like `SiteTree.Content`,
+and any other `[api:DataObject::$db]` definitions of these types.
+
+In order to allow shortcodes in your own template placeholders,
+ensure they're casted correctly:
+
+ :::php
+ class MyObject extends DataObject {
+ static $db = array('Content' => 'HTMLText');
+ static $casting = array('ContentHighlighted' => 'HTMLText');
+ public function ContentHighlighted($term) {
+ return str_replace($term, "$term", $this->Content);
+ }
+ }
+
+There is currently no way to allow shortcodes directly in template markup
+(as opposed to return values of template placeholders).
+
+## Defining Custom Shortcodes
+
+All you need to do to define a shortcode is to register a callback with the parser that will be called whenever a
+shortcode is encountered. This callback will return a string to replace the shortcode with.
+If the shortcode is used for template placeholders of type `HTMLText` or `HTMLVarchar`,
+the returned value should be valid HTML
+
+To register a shortcode you call:
+
+ ShortcodeParser::get('default')->register('myshortcode', );
+
+These parameters are passed to the callback:
+
+ - Any parameters attached to the shortcode as an associative array (keys are lower-case).
+ - Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within this
+ will not have been parsed, and can optionally be fed back into the parser.
+ - The ShortcodeParser instance used to parse the content.
+ - The shortcode tag name that was matched within the parsed content.
+
+## Example: Google Maps Iframe by Address
+
+To demonstrate how easy it is to build custom shortcodes, we'll build one to display
+a Google Map based on a provided address. Format:
+
+ [googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
+
+So we've got the address as "content" of our new `googlemap` shortcode tags,
+plus some `width` and `height` arguments. We'll add defaults to those in our shortcode parser so they're optional.
+
+ :::php
+ ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
+ $iframeUrl = sprintf(
+ 'http://maps.google.com/maps?q=%s&hnear=%s&ie=UTF8&hq=&t=m&z=14&output=embed',
+ urlencode($address),
+ urlencode($address)
+ );
+ $width = (isset($args['width']) && $args['width']) ? $args['width'] : 400;
+ $height = (isset($args['height']) && $args['height']) ? $args['height'] : 300;
+ return sprintf(
+ '',
+ $width,
+ $height,
+ $iframeUrl
+ );
+ });
+
+The hard bits are taken care of (parsing out the shortcodes), everything we need to do is a bit of string replacement.
+CMS users still need to remember the specific syntax, but these shortcodes can form the basis
+for more advanced editing interfaces (with visual placeholders). See the built-in `[embed]` shortcode as an example
+for coupling shortcodes with a form to create and edit placeholders.
+
+## Built-in Shortcodes
+
+SilverStripe comes with several shortcode parsers already.
+
+### Links
+
+Internal page links keep references to their database IDs rather than
+the URL, in order to make these links resilient against moving the target page to a different
+location in the page tree. This is done through the `[sitetree_link]` shortcode, which
+takes an `id` parameter. Example: ``
+
+Links to internal `File` database records work exactly the same, but with the `[file_link]` shortcode.
+
+### Media (Photo, Video and Rich Content)
+
+Many media formats can be embedded into websites through the `