diff --git a/docs/topics/streamfield.rst b/docs/topics/streamfield.rst
index 69f57940a..705ba1d68 100644
--- a/docs/topics/streamfield.rst
+++ b/docs/topics/streamfield.rst
@@ -471,6 +471,130 @@ To pass additional context variables to the template, block subclasses can overr
In this example, the variable ``is_happening_today`` will be made available within the block template.
+BoundBlocks and values
+----------------------
+
+As you've seen above, it's possible to assign a particular template rendering to a block. This can be done on any block type, not just StructBlocks - however, there are some extra details to be aware of. Consider the following block definition:
+
+.. code-block:: python
+
+ class HeadingBlock(blocks.CharBlock):
+ class Meta:
+ template = 'blocks/heading.html'
+
+where blocks/heading.html consists of:
+
+.. code-block:: html+django
+
+
{{ value }}
+
+This gives us a block that behaves as an ordinary text field, but wraps its output in ```` tags whenever it is rendered:
+
+.. code-block:: python
+
+ class BlogPage(Page):
+ body = StreamField([
+ # ...
+ 'heading': HeadingBlock(),
+ # ...
+ ])
+
+.. code-block:: html+django
+
+ {% for block in page.body %}
+ {% if block.block_type == 'heading' %}
+ {{ block }} {# this block will output its own ...
tags #}
+ {% endif %}
+ {% endfor %}
+
+This is a powerful feature, but it involves some complexity behind the scenes to make it work. Effectively, HeadingBlock has a double identity - logically it represents a plain Python string value, but in circumstances such as this it needs to yield a 'magic' object that knows its own custom HTML representation. This 'magic' object is an instance of ``BoundBlock`` - an object that represents the pairing of a value and its block definition. (Django developers may recognise this as the same principle behind ``BoundField`` in Django's forms framework.)
+
+Most of the time, you won't need to worry about whether you're dealing with a plain value or a BoundBlock; you can trust Wagtail to do the right thing. However, there are certain cases where the distinction becomes important. For example, consider the following setup:
+
+.. code-block:: python
+
+ class EventBlock(blocks.StructBlock):
+ heading = HeadingBlock()
+ description = blocks.TextBlock()
+ # ...
+
+ class Meta:
+ template = 'blocks/event.html'
+
+where blocks/event.html is:
+
+.. code-block:: html+django
+
+
+ {{ value.heading }}
+ - {{ value.description }}
+
+
+In this case, ``value.heading`` returns the plain string value; if this weren't the case, the comparison in ``{% if value.heading == 'Party!' %}`` would never succeed. This in turn means that ``{{ value.heading }}`` renders as the plain string, without the ```` tags.
+
+Interactions between BoundBlocks and plain values work according to the following rules:
+
+1. When iterating over the value of a StreamField or StreamBlock (as in ``{% for block in page.body %}``), you will get back a sequence of BoundBlocks.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This means that ``{{ block }}`` will always render using the block's own template, if one is supplied. More specifically, these ``block`` objects will be instances of StreamChild, which additionally provides the ``block_type`` property.
+
+2. If you have a BoundBlock instance, you can access the plain value as ``block.value``.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For example, if you had a particular page template where you wanted HeadingBlock to display as ```` rather than ````, you could write:
+
+.. code-block:: html+django
+
+ {% for block in page.body %}
+ {% if block.block_type == 'heading' %}
+ {{ block.value }}
+ {% endif %}
+ {% endfor %}
+
+3. Accessing a child of a StructBlock (as in ``value.heading``) will return a plain value; to retrieve the BoundBlock instead, use ``value.bound_blocks.heading``.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This ensures that template tags such as ``{% if value.heading == 'Party!' %}`` and ``{% image value.photo fill-320x200 %}`` work as expected. The event template above could be rewritten as follows to access the HeadingBlock content as a BoundBlock and use its own HTML representation (with ```` tags included):
+
+.. code-block:: html+django
+
+
+ {{ value.bound_block.heading }}
+ {{ value.description }}
+
+
+However, in this case it's probably more readable to make the ```` tag explicit in the EventBlock's template:
+
+.. code-block:: html+django
+
+
+
{{ value.heading }}
+ {{ value.description }}
+
+
+4. The value of a ListBlock is a plain Python list; iterating over it returns plain child values.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+5. StructBlock and StreamBlock values always know how to render their own templates, even if you only have the plain value rather than the BoundBlock.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is possible because the HTML rendering behaviour of these blocks does not interfere with their main role as a container for data - there's no "double identity" as there is for blocks like CharBlock. For example, if a StructBlock is nested in another StructBlock, as in:
+
+.. code-block:: python
+
+ class EventBlock(blocks.StructBlock):
+ heading = HeadingBlock()
+ description = blocks.TextBlock()
+ guest_speaker = blocks.StructBlock([
+ ('first_name', blocks.CharBlock()),
+ ('surname', blocks.CharBlock()),
+ ('photo', ImageChooserBlock()),
+ ], template='blocks/speaker.html')
+
+then writing ``{{ value.guest_speaker }}`` within the EventBlock's template will use the template rendering from blocks/speaker.html for that field.
+
+
Custom block types
------------------