diff --git a/docs/manual/extending/extensions/class.rst b/docs/manual/extending/extensions/class.rst
new file mode 100644
index 0000000000000000000000000000000000000000..24773a7fb870a802bb1d00cb0ae69c4d068f7d34
--- /dev/null
+++ b/docs/manual/extending/extensions/class.rst
@@ -0,0 +1,190 @@
+.. _extension-class:
+
+===============
+Extension Class
+===============
+
+The main component of an extension is a class inheriting from
+:py:class:`reviewboard.extensions.base.Extension`. It can optionally set
+the following attributes on the class:
+
+* :py:attr:`apps`
+* :py:attr:`context_processors`
+* :py:attr:`css_bundles`
+* :py:attr:`default_settings`
+* :py:attr:`has_admin_site`
+* :py:attr:`is_configurable`
+* :py:attr:`js_bundles`
+* :py:attr:`js_extensions`
+* :py:attr:`metadata`
+* :py:attr:`middleware`
+* :py:attr:`requirements`
+* :py:attr:`resources`
+
+The following are also available on an extension instance:
+
+* :py:attr:`settings`
+
+
+.. py:class:: reviewboard.extensions.base.Extension
+
+   .. py:attribute:: apps
+
+      A list of `Django apps`_ that the extension either provides or depends
+      upon.
+
+      Each "app" is a Python module path that Django will use when looking for
+      models, template tags, and more.
+
+      This does not need to include the app for the extension itself, but
+      if the extension is grouped into separate Django apps, it can list
+      those.
+
+      This setting is equivalent to modifying ``settings.INSTALLED_APPS``
+      in Django.
+
+   .. py:attribute:: context_processors
+
+      A list of `Django context processors`_, which inject variables into
+      every rendered template. Certain third-party apps depend on context
+      processors.
+
+      This setting is equivalent to modifying
+      ``settings.TEMPLATE_CONTEXT_PROCESSORS`` in Django.
+
+   .. py:attribute:: css_bundles
+
+      A list of custom CSS media bundles that can be used when rendering
+      pages.
+
+      See :ref:`extension-static-files` for more information.
+
+   .. py:attribute:: default_settings
+
+      A dictionary of default settings for the extension. These defaults
+      are used when accessing :py:attr:`settings`, if the user hasn't
+      provided a custom value. By default, this is empt.
+
+      See :ref:`extension-settings-defaults` for more information.
+
+   .. py:attribute:: has_admin_site
+
+      A boolean that indicates whether a Django admin site should be generated
+      for the extension.
+
+      If ``True``, a :guilabel:`Database` link will be shown for the
+      extension, allowing the user to inspect and modify the extension's
+      database entries. The default is ``False``.
+
+      See :ref:`extension-admin-site` for more information.
+
+   .. py:attribute:: is_configurable
+
+      A boolean indicating whether the extension supports global
+      configuration by a system administrator.
+
+      If ``True``, a :guilabel:`Configure` link will be shown for the
+      extension when enabled, taking them to the configuration page provided
+      by the extension. The default is ``False``.
+
+      See :ref:`extension-configuration` for more information.
+
+   .. py:attribute:: js_bundles
+
+      A list of custom JavaScript media bundles that can be used when
+      rendering pages.
+
+      See :ref:`extension-static-files` for more information.
+
+   .. py:attribute:: js_extensions
+
+      A list of :py:class:`reviewboard.extensions.base.JSExtension`
+      subclasses used for providing JavaScript-side extensions.
+
+      See :ref:`js-extensions` for more information.
+
+   .. py:attribute:: metadata
+
+      A dictionary providing additional information on the extension,
+      such as the name or a description.
+
+      By default, the metadata from :file:`setup.py` is used when displaying
+      information about the extension inside the administration UI. Extensions
+      can override what the user sees by setting the values in this
+      dictionary.
+
+      The following metadata keys are supported:
+
+      ``Name``
+         The human-readable name of the extension, shown in the extension
+         list.
+
+      ``Version``
+         The version of the extension. Usually, the version specified in
+         :file:`setup.py` suffices.
+
+      ``Summary``
+         A brief summary of the extension, shown in the extension list.
+
+      ``Description``
+         A longer description of the extension. As of Review Board 2.0, this
+         is not shown to the user, but it may be used in a future release.
+
+      ``Author``
+         The individual or company that authored the extension.
+
+      ``Author-email``
+         The contact e-mail address for the author of the extension.
+
+      ``Author-home-page``
+         The URL to the author's public site.
+
+      ``Home-page``
+         The URL to the extension's public site.
+
+      We generally recommend setting ``Name``, ``Summary``, and the
+      author information. ``Version`` is usually best left to the package,
+      unless there's a special way it should be presented.
+
+   .. py:attribute:: middleware
+
+      A list of `Django middleware`_ classes, which hook into various levels
+      of the HTTP request/response and page render process.
+
+      This is an advanced feature, and is generally not needed by most
+      extensions. Certain third-party apps may depend on middleware,
+      though.
+
+      This setting is equivalent to modifying
+      ``settings.MIDDLEWARE_CLASSES`` in Django.
+
+   .. py:attribute:: requirements
+
+      A list of strings providing the names of other extensions the
+      extension requires. Enabling the extension will in turn enable
+      all required extensions, and can only be enabled if the required
+      extensions can also be enabled.
+
+      See :ref:`extension-egg-dependencies` for more information.
+
+   .. py:attribute:: settings
+
+      An instance of :py:class:`djblets.extensions.settings.Settings`. This
+      attribute gives each extension an easy-to-use and persistent data store
+      for settings.
+
+      See :ref:`extension-settings` for more information.
+
+   .. py:attribute:: resources
+
+      A list of :py:class:`reviewboard.webapi.resources.WebAPIResource`
+      subclasses. This is used to extend the Web API.
+
+      See :ref:`extension-resources` for more information.
+
+
+.. _`Django apps`: https://docs.djangoproject.com/en/dev/intro/reusable-apps/
+.. _`Django context processors`:
+   https://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext
+.. _`Django middleware`:
+   https://docs.djangoproject.com/en/dev/topics/http/middleware/
diff --git a/docs/manual/extending/extensions/configuration.rst b/docs/manual/extending/extensions/configuration.rst
new file mode 100644
index 0000000000000000000000000000000000000000..db0bad15b02440351d94a4ea94de8d6ccc92ba17
--- /dev/null
+++ b/docs/manual/extending/extensions/configuration.rst
@@ -0,0 +1,189 @@
+=======================
+Extension Configuration
+=======================
+
+
+.. _extension-settings:
+
+Settings
+========
+
+Extensions are able to access, store, and modify settings that define their
+behavior.
+
+When an extension is enabled, Review Board will load any stored settings from
+the database, making them available through the :py:attr:`settings` attribute
+on the Extension.
+
+Extensions can modify the settings by changing the contents of the dictionary
+and calling :py:meth:`save`. For example:
+
+.. code-block:: python
+
+   self.settings['mybool'] = True
+   self.settings['myint'] = 42
+   self.settings['mystring'] = 'New Setting Value'
+   self.settings.save()
+
+
+.. _extension-settings-defaults:
+
+Default Settings
+----------------
+
+Any settings not explicitly saved by the extension or loaded from the database
+will be looked up in :py:attr:`default_settings`. This can be defined on the
+Extension class. For example::
+
+Here is an example extension setting :py:attr:`default_settings`:
+
+.. code-block:: python
+
+   class SampleExtension(Extension):
+       default_settings = {
+           'mybool': True,
+           'myint': 4,
+           'mystring': "I'm a string setting",
+       }
+
+
+If neither :py:attr:`settings` nor py:attr:`default_settings` contains the
+key, a :py:exc:`KeyError` exception will be thrown.
+
+
+.. _extension-configuration:
+
+Configuration Pages
+===================
+
+Extensions can provide a configuration page, allowing Review Board
+administrators to customize the behavior of the extension.
+
+By setting :py:attr:`is_configurable` to ``True`` and providing a
+:file:`admin_urls.py` file, a :guilabel:`Configure` link will be shown in the
+extension list for the extension. This is only shown when the extension is
+enabled.
+
+The extension will then need to create a page to present to the user for any
+customizable settings. Review Board provides some helpers for this, which
+will be described below.
+
+
+.. _extension-configuration-urls:
+
+Configuration URLs
+------------------
+
+When an extension is configurable, Review Board will load the extension's
+:file:`admin_urls.py`, making those URLs available. An extension can provide
+whatever it wants in here, but it's expected to proivide at least the root
+URL, designated by ``url(r'^$', ...)``. This should point to the main
+configuration page.
+
+This file follows the `Django URLs`_ format. It must provide a
+``urlpatterns`` variable, which will contain all the URL patterns.
+For example:
+
+.. code-block:: python
+
+   from django.conf.urls.defaults import patterns, url
+
+
+   urlpatterns = patterns('sample_extension.views',
+       url(r'^$', 'configure')
+   )
+
+This will call the ``configure`` function in ``sample_extension.views``
+when clickin the :guilabel:`Configure` link.
+
+.. _`Django URLs`: https://docs.djangoproject.com/en/dev/topics/http/urls/
+
+
+.. _extension-configuration-settings-form:
+
+Settings Form
+-------------
+
+Review Board makes it easy to create a basic configuration form for an
+extension. It provides views, templates, and a form class that does the hard
+work of loading settings, presenting them to the user, and saving them.
+
+To make use of the provided configuration forms, you'll want to:
+
+1. Define a new form class that inherits from
+   :py:class:`djblets.extensions.forms.SettingsForm`
+
+2. Create a new ``url()`` entry in :File:`admin_urls.py` that makes use
+   of the provided configuration view, passing your extension and form
+   classes.
+
+Here is an example form class:
+
+.. code-block:: python
+
+   from django import forms
+   from djblets.extensions.forms import SettingsForm
+
+
+   class SampleExtensionSettingsForm(SettingsForm):
+       field1 = forms.IntegerField(min_value=0, initial=1,
+                                   help_text="Field 1")
+
+
+And here is an example URL pattern for the form:
+
+.. code-block:: python
+
+   from django.conf.urls.defaults import patterns, url
+
+   from sample_extension.extension import SampleExtension
+   from sample_extension.forms import SampleExtensionSettingsForm
+
+
+   urlpatterns = patterns('',
+       url(r'^$',
+           'reviewboard.extensions.views.configure_extension',
+           {
+               'ext_class': SampleExtension,
+               'form_class': SampleExtensionSettingsForm,
+           }),
+   )
+
+
+.. _extension-admin-site:
+
+Admin Site (Database Browser)
+=============================
+
+By setting :py:attr:`has_admin_site` to ``True``, an extension will be given
+its own Django database administration site. A button labeled
+:guilabel:`Database` will appear in the list of installed extensions, linking
+to that site.
+
+The extension will also have a :py:attr:`admin_site` attribute that points to
+the :py:class:`django.contrib.admin.sites.AdminSite` used. This is provided
+automatically, and is used primarily for the registration of models.
+
+Only models that are registered will appear in the database browser. You can
+see the documentation on the `Django admin site`_ for details on how this
+works. For example:
+
+.. code-block:: python
+
+   from reviewboard.extensions.base import get_extension_manager
+
+   from sample_extension.extension import SampleExtension
+   from sample_extension.models import SampleModel
+
+
+   # You must get the loaded instance of the extension to register to the
+   # admin site.
+   extension_manager = get_extension_manager()
+   extension = extension_manager.get_enabled_extension(SampleExtension.id)
+
+   # Register the Model so it will show up in the admin site.
+   extension.admin_site.register(SampleModel)
+
+
+.. _`Django Admin Site`:
+   https://docs.djangoproject.com/en/dev/ref/contrib/admin/
diff --git a/docs/manual/extending/extensions/distribution.rst b/docs/manual/extending/extensions/distribution.rst
index 30964bbac5b708ee1b0d5d6172cd2db01805527d..5d65701db95b5ae4553b96a873e7e4083e25de8f 100644
--- a/docs/manual/extending/extensions/distribution.rst
+++ b/docs/manual/extending/extensions/distribution.rst
@@ -24,11 +24,11 @@ documentation for a full description of features.
 Entry Point
 -----------
 
-.. highlight:: python
-
 To facilitate the auto-detection of installed extensions, a
 ``reviewboard.extensions`` entry point must be defined for each
-:ref:`extension-class`. Here is an example entry point definition::
+:ref:`extension-class`. Here is an example entry point definition:
+
+.. code-block:: python
 
       entry_points={
            'reviewboard.extensions':
@@ -37,7 +37,9 @@ To facilitate the auto-detection of installed extensions, a
 
 This defines an entry point for the :py:class:`SampleExtension` class from
 the :py:mod:`sample_extension.extension` module. Here is an example of
-a full :file:`setup.py` file defining this entry point::
+a full :file:`setup.py` file defining this entry point:
+
+.. code-block:: python
 
    from reviewboard.extensions.packaging import setup
 
@@ -73,7 +75,9 @@ along with your extension.
 See :ref:`extension-static-files` for more information on bundles.
 
 If you have other files you need to include, such as templates, you can list
-them in the ``package_data`` section in setup.py. For example::
+them in the ``package_data`` section in :file:`setup.py`. For example:
+
+.. code-block:: python
 
        package_data={
            'sample_extension': [
@@ -82,7 +86,9 @@ them in the ``package_data`` section in setup.py. For example::
            ],
        }
 
-Here is an example of a full setup.py file including the static files::
+Here is an example of a full setup.py file including the static files:
+
+.. code-block:: python
 
    from reviewboard.extensions.packaging import setup
 
@@ -116,7 +122,9 @@ Dependencies
 
 Any dependencies of the extension are defined in the :file:`setup.py` file
 using :py:attr:`install_requires`. Here is an example of a full
-:file`setup.py` file including a dependency::
+:file`setup.py` file including a dependency:
+
+.. code-block:: python
 
    from reviewboard.extensions.packaging import setup
 
@@ -148,14 +156,13 @@ declare a list of additional extensions it requires. This requirements list
 gives the name of each extension that must be enabled before allowing the
 extension itself to be enabled. This list is declared by setting the
 :py:attr:`requirements` attribute. Here is an example of an extension
-defining a requirements list::
+defining a requirements list:
+
+.. code-block:: python
 
    class SampleExtension(Extension):
        requirements = ['other_extension.extension.OtherExtension']
 
-       def __init__(self, *args, **kwargs):
-           super(RBWebHooksExtension, self).__init__(*args, **kwargs)
-
 
 .. _extension-egg-developing:
 
@@ -165,9 +172,11 @@ Developing With a Python Egg
 In order for Review Board to detect an extension, the Python Egg must be
 generated using the :file:`setup.py` file, and installed. During development
 this can be done by installing a link in the Python installation to the
-source directory of your extension. This is accomplished by running::
+source directory of your extension. This is accomplished by running:
+
+.. code-block:: sh
 
-   python setup.py develop
+   $ python setup.py develop
 
 If changes are made to the setup.py file this should be executed again.
 
diff --git a/docs/manual/extending/extensions/file-layout.rst b/docs/manual/extending/extensions/file-layout.rst
new file mode 100644
index 0000000000000000000000000000000000000000..9ca296789960dffc28dc8121bbb019c77b46d2ff
--- /dev/null
+++ b/docs/manual/extending/extensions/file-layout.rst
@@ -0,0 +1,127 @@
+===========
+File Layout
+===========
+
+Extensions must follow a certain file structure in order to be recognized and
+loaded by Review Board. They are distributed inside Python Egg packages, which
+must follow a few conventions. See :ref:`extension-python-egg` for more
+information.
+
+.. note::
+   The Review Board extension system has been designed to follow many
+   of Django_'s conventions. The structure of an extension package tries
+   to mirror that of Django apps.
+
+The main constituent of an extension is a class which inherits from the
+Extension base class :py:class:`reviewboard.extensions.base.Extension`.
+
+The Review Board repository contains a script for generating the
+initial code an extension requires. See :ref:`extension-generator` for
+more information.
+
+
+.. _Django: https://www.djangoproject.com/
+
+
+Required Files
+--------------
+
+At minimum, an extension requires the following files:
+
+*  :ref:`setup.py <extension-example-files-setup.py>`
+*  *extensiondir*/:ref:`__init__.py <extension-example-files-__init__.py>`
+*  *extensiondir*/:ref:`extension.py <extension-example-files-extension.py>`
+
+
+The following are a description and example of each file. In each example
+*extensiondir* has been replaced with the extension's package,
+`'sample_extension'`:
+
+.. _extension-example-files-setup.py:
+
+**setup.py**
+   This is the file used to create the Python Egg. It defines the
+   :ref:`extension-entry-point` along with other meta-data. See
+   :ref:`extension-distribution` for a description of features relevant to
+   Review Board extensions. For example:
+
+   .. code-block:: python
+
+      from reviewboard.extensions.packaging import setup
+
+
+      PACKAGE = "sample_extension"
+      VERSION = "0.1"
+
+
+      setup(
+          name=PACKAGE,
+          version=VERSION,
+          description='Description of extension package.',
+          author='Your Name',
+          packages=['sample_extension'],
+          entry_points={
+              'reviewboard.extensions':
+                  '%s = sample_extension.extension:SampleExtension' % PACKAGE,
+          },
+      )
+
+   See :ref:`extension-distribution` and the :py:mod:`setuptools` documentation
+   for more information.
+
+.. _extension-example-files-__init__.py:
+
+sample_extension/**__init__.py**
+   This file indicates the sample_extension is a Python package. It is
+   generally left blank.
+
+.. _extension-example-files-extension.py:
+
+sample_extension/**extension.py**
+   This is the main module of the extension. The Extension subclass should
+   be defined here. For example:
+
+   .. code-block:: python
+
+      from reviewboard.extensions.base import Extension
+
+
+      class SampleExtension(Extension):
+          def initialize(self):
+              # Your extension initialization code belongs here.
+
+   This file will often be where you'll define any hooks, utility functions,
+   and extension metadata you may need. Throughout this guide, we'll cover
+   the various things you may place in this file.
+
+
+Optional Files
+--------------
+
+Review Board also expects extensions to follow a few other conventions when
+naming files. The following files serve a special purpose:
+
+**models.py**
+   An extension may define Django models in this file. The corresponding
+   tables will be created in the database when the extension is loaded. See
+   :ref:`extension-models` for more information.
+
+**models/**
+   As an alternative to using :file:`models.py`, a Python package may be
+   created in a :file:`models/` directory, which may contain files with other
+   models. Like any Python module directory, it must also contain an
+   :file:`__init__.py`.
+
+**admin_urls.py**
+   An extension may define URLs for configuration in the administration UI.
+
+   This file is only used when :py:attr:`is_configurable` is set ``True``.
+   For more information, see :ref:`extension-configuration-urls`.
+
+**admin.py**
+   This file allows an extension to register its models in its own section
+   of the administration UI, allowing administrators to browse the content
+   in the database.
+
+   This file is only used when :py:attr:`has_admin_site` is set ``True``.
+   For more information, see :ref:`extension-admin-site`.
diff --git a/docs/manual/extending/extensions/index.rst b/docs/manual/extending/extensions/index.rst
index 81568d03c162a1ee7ebb7c7bf884697110be2046..341da3b0d7613aef2330e56657c8dee840617736 100644
--- a/docs/manual/extending/extensions/index.rst
+++ b/docs/manual/extending/extensions/index.rst
@@ -6,7 +6,12 @@ Writing Review Board Extensions
    :maxdepth: 2
 
    overview
+   file-layout
+   class
+   configuration
+   models
    hooks/index
    static-files
    review-ui
+   webapi
    distribution
diff --git a/docs/manual/extending/extensions/models.rst b/docs/manual/extending/extensions/models.rst
new file mode 100644
index 0000000000000000000000000000000000000000..6bf82b529a1c6099ddb9f8a0a54a78c90b3e1ea8
--- /dev/null
+++ b/docs/manual/extending/extensions/models.rst
@@ -0,0 +1,37 @@
+.. _extension-models:
+
+===============
+Database Models
+===============
+
+Extensions are able to provide `Django Models`_, which are database tables
+under the control of the extension. Review Board handles registering these
+models, creating the database tables, and performing any database schema
+migrations the extension defines.
+
+Extensions use the same convention as `Django apps`_ when defining
+Models. In order to define new Models, a :file:`models.py` file, or a
+:file:`models/` directory constituting a Python package needs to be created.
+
+Here is an example :file:`models.py` file:
+
+.. code-block:: python
+
+   from django.db import models
+
+
+   class MyExtensionsSampleModel(models.Model):
+       name = models.CharField(max_length=128)
+       enabled = models.BooleanField(default=False)
+
+See the `Django Models`_ documentation for more information on how to
+write a model, and `Django Evolution`_ for information on how to write
+database schema migrations.
+
+.. note::
+   When an extension is disabled, tables for its models remain in the
+   database. These should generally not interfere with anything.
+
+
+.. _`Django Models`: https://docs.djangoproject.com/en/dev/topics/db/models/
+.. _`Django Evolution`: http://django-evolution.googlecode.com/
diff --git a/docs/manual/extending/extensions/overview.rst b/docs/manual/extending/extensions/overview.rst
index 73285e5c431cf4ef1c0e15aeb7c38058ecf251ce..71fd2fc814dafd1e34be86721b58ffc4b9e160d5 100644
--- a/docs/manual/extending/extensions/overview.rst
+++ b/docs/manual/extending/extensions/overview.rst
@@ -20,623 +20,16 @@ However, many of the features discussed here were added or changed in Review
 Board 2.0.
 
 
-Extension Structure
-===================
-
-Extensions must follow a certain structure in order to be recognized and
-loaded by Review Board. They are distributed inside Python Egg packages,
-which must follow a few conventions. See :ref:`extension-python-egg` for more
-information.
-
-.. note::
-   The Review Board extension system has been designed to follow many
-   of Django_'s conventions. The structure of an extension package tries
-   to mirror that of Django apps.
-
-The main constituent of an extension is a class which inherits from the
-Extension base class :py:class:`reviewboard.extensions.base.Extension`.
-
-The Review Board repository contains a script for generating the
-initial code an extension requires. See :ref:`extension-generator` for
-more information.
-
-
-.. _Django: https://www.djangoproject.com/
-
-
-Required Files
---------------
-
-At minimum, an extension requires the following files:
-
-*  :ref:`setup.py <extension-example-files-setup.py>`
-*  *extensiondir*/:ref:`__init__.py <extension-example-files-__init__.py>`
-*  *extensiondir*/:ref:`extension.py <extension-example-files-extension.py>`
-
-
-The following are a description and example of each file. In each example
-*extensiondir* has been replaced with the extension's package,
-`'sample_extension'`:
-
-.. _extension-example-files-setup.py:
-
-**setup.py**
-   This is the file used to create the Python Egg. It defines the
-   :ref:`extension-entry-point` along with other meta-data. See
-   :ref:`extension-distribution` for a description of features relevant to
-   Review Board extensions. For example::
-
-      from reviewboard.extensions.packaging import setup
-
-
-      PACKAGE = "sample_extension"
-      VERSION = "0.1"
-
-      setup(
-          name=PACKAGE,
-          version=VERSION,
-          description='Description of extension package.',
-          author='Your Name',
-          packages=['sample_extension'],
-          entry_points={
-              'reviewboard.extensions':
-                  '%s = sample_extension.extension:SampleExtension' % PACKAGE,
-          },
-      )
-
-   See :ref:`extension-distribution` and the :py:mod:`setuptools` documentation
-   for more information.
-
-.. _extension-example-files-__init__.py:
-
-sample_extension/**__init__.py**
-   This file indicates the sample_extension is a Python package. It is
-   generally left blank.
-
-.. _extension-example-files-extension.py:
-
-sample_extension/**extension.py**
-   This is the main module of the extension. The Extension subclass should
-   be defined here. For example::
-
-      from reviewboard.extensions.base import Extension
-
-
-      class SampleExtension(Extension):
-          def initialize(self):
-              # Your extension initialization code belongs here.
-
-   This file will often be where you'll define any hooks, utility functions,
-   and extension metadata you may need. Throughout this guide, we'll cover
-   the various things you may place in this file.
-
-
-Optional Files
---------------
-
-Review Board also expects extensions to follow a few other conventions when
-naming files. The following files serve a special purpose:
-
-**models.py**
-   An extension may define Django models in this file. The corresponding
-   tables will be created in the database when the extension is loaded. See
-   :ref:`extension-models` for more information.
-
-**models/**
-   As an alternative to using :file:`models.py`, a Python package may be
-   created in a :file:`models/` directory, which may contain files with other
-   models. Like any Python module directory, it must also contain an
-   :file:`__init__.py`.
-
-**admin_urls.py**
-   An extension may define URLs for configuration in the administration UI.
-
-   This file is only used when :py:attr:`is_configurable` is set ``True``.
-   For more information, see :ref:`extension-configuration-urls`.
-
-**admin.py**
-   This file allows an extension to register its models in its own section
-   of the administration UI, allowing administrators to browse the content
-   in the database.
-
-   This file is only used when :py:attr:`has_admin_site` is set ``True``.
-   For more information, see :ref:`extension-admin-site`.
-
-
-.. _extension-class:
-
-Extension Class
-===============
-
-The main component of an extension is a class inheriting from
-:py:class:`reviewboard.extensions.base.Extension`. It can optionally set
-the following attributes on the class:
-
-* :py:attr:`apps`
-* :py:attr:`context_processors`
-* :py:attr:`css_bundles`
-* :py:attr:`default_settings`
-* :py:attr:`has_admin_site`
-* :py:attr:`is_configurable`
-* :py:attr:`js_bundles`
-* :py:attr:`js_extensions`
-* :py:attr:`metadata`
-* :py:attr:`middleware`
-* :py:attr:`requirements`
-* :py:attr:`resources`
-
-The following are also available on an extension instance:
-
-* :py:attr:`settings`
-
-
-.. py:class:: reviewboard.extensions.base.Extension
-
-   .. py:attribute:: apps
-
-      A list of `Django apps`_ that the extension either provides or depends
-      upon.
-
-      Each "app" is a Python module path that Django will use when looking for
-      models, template tags, and more.
-
-      This does not need to include the app for the extension itself, but
-      if the extension is grouped into separate Django apps, it can list
-      those.
-
-      This setting is equivalent to modifying ``settings.INSTALLED_APPS``
-      in Django.
-
-   .. py:attribute:: context_processors
-
-      A list of `Django context processors`_, which inject variables into
-      every rendered template. Certain third-party apps depend on context
-      processors.
-
-      This setting is equivalent to modifying
-      ``settings.TEMPLATE_CONTEXT_PROCESSORS`` in Django.
-
-   .. py:attribute:: css_bundles
-
-      A list of custom CSS media bundles that can be used when rendering
-      pages.
-
-      See :ref:`extension-static-files` for more information.
-
-   .. py:attribute:: default_settings
-
-      A dictionary of default settings for the extension. These defaults
-      are used when accessing :py:attr:`settings`, if the user hasn't
-      provided a custom value. By default, this is empt.
-
-      See :ref:`extension-settings-defaults` for more information.
-
-   .. py:attribute:: has_admin_site
-
-      A boolean that indicates whether a Django admin site should be generated
-      for the extension.
-
-      If ``True``, a :guilabel:`Database` link will be shown for the
-      extension, allowing the user to inspect and modify the extension's
-      database entries. The default is ``False``.
-
-      See :ref:`extension-admin-site` for more information.
-
-   .. py:attribute:: is_configurable
-
-      A boolean indicating whether the extension supports global
-      configuration by a system administrator.
-
-      If ``True``, a :guilabel:`Configure` link will be shown for the
-      extension when enabled, taking them to the configuration page provided
-      by the extension. The default is ``False``.
-
-      See :ref:`extension-configuration` for more information.
-
-   .. py:attribute:: js_bundles
-
-      A list of custom JavaScript media bundles that can be used when
-      rendering pages.
-
-      See :ref:`extension-static-files` for more information.
-
-   .. py:attribute:: js_extensions
-
-      A list of :py:class:`reviewboard.extensions.base.JSExtension`
-      subclasses used for providing JavaScript-side extensions.
-
-      See :ref:`js-extensions` for more information.
-
-   .. py:attribute:: metadata
-
-      A dictionary providing additional information on the extension,
-      such as the name or a description.
-
-      By default, the metadata from :file:`setup.py` is used when displaying
-      information about the extension inside the administration UI. Extensions
-      can override what the user sees by setting the values in this
-      dictionary.
-
-      The following metadata keys are supported:
-
-      ``Name``
-         The human-readable name of the extension, shown in the extension
-         list.
-
-      ``Version``
-         The version of the extension. Usually, the version specified in
-         :file:`setup.py` suffices.
-
-      ``Summary``
-         A brief summary of the extension, shown in the extension list.
-
-      ``Description``
-         A longer description of the extension. As of Review Board 2.0, this
-         is not shown to the user, but it may be used in a future release.
-
-      ``Author``
-         The individual or company that authored the extension.
-
-      ``Author-email``
-         The contact e-mail address for the author of the extension.
-
-      ``Author-home-page``
-         The URL to the author's public site.
-
-      ``Home-page``
-         The URL to the extension's public site.
-
-      We generally recommend setting ``Name``, ``Summary``, and the
-      author information. ``Version`` is usually best left to the package,
-      unless there's a special way it should be presented.
-
-   .. py:attribute:: middleware
-
-      A list of `Django middleware`_ classes, which hook into various levels
-      of the HTTP request/response and page render process.
-
-      This is an advanced feature, and is generally not needed by most
-      extensions. Certain third-party apps may depend on middleware,
-      though.
-
-      This setting is equivalent to modifying
-      ``settings.MIDDLEWARE_CLASSES`` in Django.
-
-   .. py:attribute:: requirements
-
-      A list of strings providing the names of other extensions the
-      extension requires. Enabling the extension will in turn enable
-      all required extensions, and can only be enabled if the required
-      extensions can also be enabled.
-
-      See :ref:`extension-egg-dependencies` for more information.
-
-   .. py:attribute:: settings
-
-      An instance of :py:class:`djblets.extensions.settings.Settings`. This
-      attribute gives each extension an easy-to-use and persistent data store
-      for settings.
-
-      See :ref:`extension-settings` for more information.
-
-   .. py:attribute:: resources
-
-      A list of :py:class:`reviewboard.webapi.resources.WebAPIResource`
-      subclasses. This is used to extend the Web API.
-
-      See :ref:`extension-resources` for more information.
-
-
-.. _`Django apps`: https://docs.djangoproject.com/en/dev/intro/reusable-apps/
-.. _`Django context processors`:
-   https://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext
-.. _`Django middleware`:
-   https://docs.djangoproject.com/en/dev/topics/http/middleware/
-
-
-.. _extension-models:
-
-Models
-======
-
-Extensions are able to provide `Django Models`_, which are database tables
-under the control of the extension. Review Board handles registering these
-models, creating the database tables, and performing any database schema
-migrations the extension defines.
-
-Extensions use the same convention as `Django apps`_ when defining
-Models. In order to define new Models, a :file:`models.py` file, or a
-:file:`models/` directory constituting a Python package needs to be created.
-
-Here is an example :file:`models.py` file::
-
-   from django.db import models
-
-
-   class MyExtensionsSampleModel(models.Model):
-       name = models.CharField(max_length=128)
-       enabled = models.BooleanField(default=False)
-
-See the `Django Models`_ documentation for more information on how to
-write a model, and `Django Evolution`_ for information on how to write
-database schema migrations.
-
-.. note::
-   When an extension is disabled, tables for its models remain in the
-   database. These should generally not interfere with anything.
-
-
-.. _`Django Models`: https://docs.djangoproject.com/en/dev/topics/db/models/
-.. _`Django Evolution`: http://django-evolution.googlecode.com/
-
-
-.. _extension-settings:
-
-Settings
-========
-
-Extensions are able to access, store, and modify settings that define their
-behavior.
-
-When an extension is enabled, Review Board will load any stored settings from
-the database, making them available through the :py:attr:`settings` attribute
-on the Extension.
-
-Extensions can modify the settings by changing the contents of the dictionary
-and calling :py:meth:`save`. For example::
-
-   self.settings['mybool'] = True
-   self.settings['myint'] = 42
-   self.settings['mystring'] = 'New Setting Value'
-   self.settings.save()
-
-
-.. _extension-settings-defaults:
-
-Default Settings
-----------------
-
-Any settings not explicitly saved by the extension or loaded from the database
-will be looked up in :py:attr:`default_settings`. This can be defined on the
-Extension class. For example::
-
-Here is an example extension setting :py:attr:`default_settings`::
-
-   class SampleExtension(Extension):
-       default_settings = {
-           'mybool': True,
-           'myint': 4,
-           'mystring': 'I'm a string setting',
-       }
-
-
-If neither :py:attr:`settings` nor py:attr:`default_settings` contains the
-key, a :py:exc:`KeyError` exception will be thrown.
-
-
-.. _extension-configuration:
-
-Configuration
-=============
-
-Extensions can provide a configuration page, allowing Review Board
-administrators to customize the behavior of the extension.
-
-By setting :py:attr:`is_configurable` to ``True`` and providing a
-:file:`admin_urls.py` file, a :guilabel:`Configure` link will be shown in the
-extension list for the extension. This is only shown when the extension is
-enabled.
-
-The extension will then need to create a page to present to the user for any
-customizable settings. Review Board provides some helpers for this, which
-will be described below.
-
-
-.. _extension-configuration-urls:
-
-Configuration URLs
-------------------
-
-When an extension is configurable, Review Board will load the extension's
-:file:`admin_urls.py`, making those URLs available. An extension can provide
-whatever it wants in here, but it's expected to proivide at least the root
-URL, designated by ``url(r'^$', ...)``. This should point to the main
-configuration page.
-
-This file follows the `Django URLs`_ format. It must provide a
-``urlpatterns`` variable, which will contain all the URL patterns.
-For example::
-
-   from django.conf.urls.defaults import patterns, url
-
-
-   urlpatterns = patterns('sample_extension.views',
-       url(r'^$', 'configure')
-   )
-
-This will call the ``configure`` function in ``sample_extension.views``
-when clickin the :guilabel:`Configure` link.
-
-.. _`Django URLs`: https://docs.djangoproject.com/en/dev/topics/http/urls/
-
-
-.. _extension-configuration-settings-form:
-
-Settings Form
--------------
-
-Review Board makes it easy to create a basic configuration form for an
-extension. It provides views, templates, and a form class that does the hard
-work of loading settings, presenting them to the user, and saving them.
-
-To make use of the provided configuration forms, you'll want to:
-
-1. Define a new form class that inherits from
-   :py:class:`djblets.extensions.forms.SettingsForm`
-
-2. Create a new ``url()`` entry in :File:`admin_urls.py` that makes use
-   of the provided configuration view, passing your extension and form
-   classes.
-
-Here is an example form class::
-
-   from django import forms
-   from djblets.extensions.forms import SettingsForm
-
-
-   class SampleExtensionSettingsForm(SettingsForm):
-       field1 = forms.IntegerField(min_value=0, initial=1,
-                                  help_text="Field 1")
-
-
-And here is an example URL pattern for the form::
-
-   from django.conf.urls.defaults import patterns, url
-
-   from sample_extension.extension import SampleExtension
-   from sample_extension.forms import SampleExtensionSettingsForm
-
-
-   urlpatterns = patterns('',
-       url(r'^$',
-           'reviewboard.extensions.views.configure_extension',
-           {
-               'ext_class': SampleExtension,
-               'form_class': SampleExtensionSettingsForm,
-           }),
-   )
-
-
-.. _extension-admin-site:
-
-Admin Site (Database Browser)
-=============================
-
-By setting :py:attr:`has_admin_site` to ``True``, an extension will be given
-its own Django database administration site. A button labeled
-:guilabel:`Database` will appear in the list of installed extensions, linking
-to that site.
-
-The extension will also have a :py:attr:`admin_site` attribute that points to
-the :py:class:`django.contrib.admin.sites.AdminSite` used. This is provided
-automatically, and is used primarily for the registration of models.
-
-Only models that are registered will appear in the database browser. You can
-see the documentation on the `Django admin site`_ for details on how this
-works. For example::
-
-   from reviewboard.extensions.base import get_extension_manager
-
-   from sample_extension.extension import SampleExtension
-   from sample_extension.models import SampleModel
-
-
-   # You must get the loaded instance of the extension to register to the
-   # admin site.
-   extension_manager = get_extension_manager()
-   extension = extension_manager.get_enabled_extension(SampleExtension.id)
-
-   # Register the Model so it will show up in the admin site.
-   extension.admin_site.register(SampleModel)
-
-
-.. _`Django Admin Site`:
-   https://docs.djangoproject.com/en/dev/ref/contrib/admin/
-
-
-.. _extension-resources:
-
-Extending the Web API
-=====================
-
-Each extension has a very basic API resource that clients can use to fetch
-details on the extension, such as the name, URLs, and whether it's enabled.
-
-Extensions can extend this to provide even more resources, which can be used
-to retrieve or modify any information the extension chooses. They do this by
-creating :py:class:`reviewboard.webapi.resources.WebAPIResource` subclasses
-and listing an instance of each that you want as a child of the extension's
-resource in :py:attr:`resources` attribute.
-
-Resources are complex, but are explained in detail in the Djblets
-`WebAPIResource code`_.
-
-.. _`WebAPIResource code`:
-   https://github.com/djblets/djblets/blob/master/djblets/webapi/resources.py
-
-
-For example, a resource for creating and publishing a simplified review may
-look like::
-
-   from django.core.exceptions import ObjectDoesNotExist
-   from djblets.webapi.decorators import (webapi_login_required,
-                                          webapi_response_errors,
-                                          webapi_request_fields)
-   from djblets.webapi.errors import DOES_NOT_EXIST
-   from reviewboard.webapi.decorators import webapi_check_local_site
-   from reviewboard.reviews.models import Review
-   from reviewboard.webapi.resources import (WebAPIResource,
-                                             review_Request_resource)
-
-   class SampleExtensionResource(WebAPIResource):
-       """Resource for creating reviews"""
-       name = 'sample_extension_review'
-       uri_name = 'review'
-       allowed_methods = ('POST',)
-
-       def has_access_permissions(self, request, *args, **kwargs):
-           return review_request.is_accessible_by(request.user)
-
-       @webapi_check_local_site
-       @webapi_login_required
-       @webapi_response_errors(DOES_NOT_EXIST)
-       @webapi_request_fields(
-           required={
-               'review_request_id': {
-                   'type': int,
-                   'description': 'The ID of the review request',
-               },
-           },
-       )
-       def create(self, request, review_request_id, *args, **kwargs):
-           try:
-               review_request = review_request_resource.get_object(
-                   request, review_request_id, *args, **kwargs)
-           except ObjectDoesNotExist:
-               return DOES_NOT_EXIST
-
-           new_review = Review.objects.create(
-               review_request=review_request,
-               user=request.user,
-               body_top='Sample review body')
-           new_review.publish(user=request.user)
-
-           return 201, {
-               self.item_result_key: new_review
-           }
-
-   sample_review_resource = SampleExtensionResource()
-
-
-The extension would then make use of this with::
-
-   class SampleExtension(Extension):
-       resources = [sample_review_resource]
-
-
-With this, one would be able to POST to this resource to create reviews that
-contained the text "Sample review body". This API endpoint would be registered
-at
-``/api/extensions/sample_extension.extension.SampleExtension/reviews/``.
-
-
 .. _extension-generator:
 
 Extension Boilerplate Generator
 ===============================
 
-The Review Board repository contains a script for generating the boilerplate
-code for a new extension. This script is part of the Review Board tree and can
-be run by typing::
+To help you get started writing an extension, the Review Board repository
+provides a script to provide extension boilerplate. To run the generator,
+check out the `Review Board tree`_ and run::
 
    ./contrib/tools/generate_extension.py
+
+
+.. _`Review Board tree`: https://github.com/reviewboard/reviewboard/
diff --git a/docs/manual/extending/extensions/review-ui.rst b/docs/manual/extending/extensions/review-ui.rst
index 7f9b8f49b399d814a4b6422f23ceb92840181f03..72a94442153c4a4f9ecea67ae69091e850b58176 100644
--- a/docs/manual/extending/extensions/review-ui.rst
+++ b/docs/manual/extending/extensions/review-ui.rst
@@ -29,7 +29,9 @@ UIs.  This can be using :py:class:`reviewboard.extensions.hooks.ReviewUIHook`
 directly, using a subclass of it. :py:class:`ReviewUIHook` expects a list of
 Review UIs as argument in addition to the extension instance.
 
-Example: **XMLReviewUIExtension**::
+Example: **XMLReviewUIExtension**:
+
+.. code-block:: python
 
     class XMLReviewUIExtension(Extension):
         def __init__(self, *args, **kwargs):
@@ -57,7 +59,9 @@ define the following class variables:
 *
     **object_key**: a unique name to identify this Review UI
 
-Example: **XMLReviewUI**::
+Example: **XMLReviewUI**:
+
+.. code-block:: python
 
     import logging
 
@@ -79,7 +83,9 @@ function or what it returns, but it should be in agreement with logic specified
 in its corresponding template.
 
 Example: **render()** in **XMLReviewUI**. This simply uses the pygments API
-to convert raw XML into syntax-highlighted HTML::
+to convert raw XML into syntax-highlighted HTML:
+
+.. code-block:: python
 
     def render(self):
         data_string = ""
@@ -104,11 +110,11 @@ to convert raw XML into syntax-highlighted HTML::
 ReviewUI Template
 -----------------
 
-.. highlight:: html
-
 Here is the template that corresponds to the above Review UI:
 
-:file:`xml_review_ui_extension/templates/xml_review_ui_extension/xml.html`::
+:file:`xml_review_ui_extension/templates/xml_review_ui_extension/xml.html`:
+
+.. code-block:: html+django
 
     {% extends base_template %}
     {% load i18n %}
@@ -151,11 +157,11 @@ Here is the template that corresponds to the above Review UI:
 ReviewUI JavaScript
 -------------------
 
-.. highlight:: javascript
-
 Here are the corresponding JavaScript used in the above template.
 
-:file:`xml_review_ui_extension/static/js/XMLReviewableModel.js`::
+:file:`xml_review_ui_extension/static/js/XMLReviewableModel.js`:
+
+.. code-block:: javascript
 
     /*
      * Provides review capabilities for XML files.
@@ -167,7 +173,9 @@ Here are the corresponding JavaScript used in the above template.
     });
 
 
-:file:`xml_review_ui_extension/static/js/XMLReviewableView.js`::
+:file:`xml_review_ui_extension/static/js/XMLReviewableView.js`:
+
+.. code-block:: javascript
 
     /*
      * Displays a review UI for XML files.
diff --git a/docs/manual/extending/extensions/static-files.rst b/docs/manual/extending/extensions/static-files.rst
index 2e9b68a340539f005d1bd50385cbc68b5f0b07dd..1b27350b5f54f65c9da4c8be5a1a3b9a53f6ea3a 100644
--- a/docs/manual/extending/extensions/static-files.rst
+++ b/docs/manual/extending/extensions/static-files.rst
@@ -13,7 +13,9 @@ bundles, listed in the extension class's :py:attr:`css_bundles` or
 the same type that are grouped together under a name.
 
 The format for a bundle is the same for CSS and JavaScript. Here's an
-example::
+example:
+
+.. code-block:: python
 
     class MyExtension(Extension):
         css_bundles = {
@@ -55,7 +57,9 @@ node.js_, LESS_, and UglifyJS_.
 Most Linux distributions have a package for node.js. Otherwise, follow the
 instructions on the node.js_ home page.
 
-Once you install node.js, run::
+Once you install node.js, run:
+
+.. code-block:: sh
 
     $ sudo npm install -g less uglifyjs
 
@@ -68,12 +72,12 @@ Once you install node.js, run::
 Loading Bundles in Templates
 ----------------------------
 
-.. highlight:: django
-
 When creating a template for a :ref:`extensions-template-hook`, you may need
 to load one of your bundles. This can be done through the ``ext_css_bundle``
 or ``ext_js_bundle`` template tags, by passing the extension variable
-(provided to your template) and the bundle name to load. For example::
+(provided to your template) and the bundle name to load. For example:
+
+.. code-block:: django
 
     {% ext_css_bundle extension "my-bundle" %}
 
@@ -99,13 +103,13 @@ not conflict with other extensions' rules.
 JavaScript
 ----------
 
-.. highlight:: javascript
-
 JavaScript files have access to the Review Board JavaScript codebase,
 jQuery, Backbone.js, and other shipped libraries.
 
 It is recommended that you namespace all the code in your JavaScript file, and
-wrap the file in a closure, as so::
+wrap the file in a closure, as so:
+
+.. code-block:: javascript
 
     (function() {
 
diff --git a/docs/manual/extending/extensions/webapi.rst b/docs/manual/extending/extensions/webapi.rst
new file mode 100644
index 0000000000000000000000000000000000000000..8e997fae0b7933207333e89b06dd39d12f7a69c6
--- /dev/null
+++ b/docs/manual/extending/extensions/webapi.rst
@@ -0,0 +1,89 @@
+.. _extension-resources:
+
+=====================
+Extending the Web API
+=====================
+
+Each extension has a very basic API resource that clients can use to fetch
+details on the extension, such as the name, URLs, and whether it's enabled.
+
+Extensions can extend this to provide even more resources, which can be used
+to retrieve or modify any information the extension chooses. They do this by
+creating :py:class:`reviewboard.webapi.resources.WebAPIResource` subclasses
+and listing an instance of each that you want as a child of the extension's
+resource in :py:attr:`resources` attribute.
+
+Resources are complex, but are explained in detail in the Djblets
+`WebAPIResource code`_.
+
+.. _`WebAPIResource code`:
+   https://github.com/djblets/djblets/blob/master/djblets/webapi/resources.py
+
+
+For example, a resource for creating and publishing a simplified review may
+look like:
+
+.. code-block:: python
+
+   from django.core.exceptions import ObjectDoesNotExist
+   from djblets.webapi.decorators import (webapi_login_required,
+                                          webapi_response_errors,
+                                          webapi_request_fields)
+   from djblets.webapi.errors import DOES_NOT_EXIST
+   from reviewboard.webapi.decorators import webapi_check_local_site
+   from reviewboard.reviews.models import Review
+   from reviewboard.webapi.resources import (WebAPIResource,
+                                             review_Request_resource)
+
+
+   class SampleExtensionResource(WebAPIResource):
+       """Resource for creating reviews"""
+       name = 'sample_extension_review'
+       uri_name = 'review'
+       allowed_methods = ('POST',)
+
+       def has_access_permissions(self, request, *args, **kwargs):
+           return review_request.is_accessible_by(request.user)
+
+       @webapi_check_local_site
+       @webapi_login_required
+       @webapi_response_errors(DOES_NOT_EXIST)
+       @webapi_request_fields(
+           required={
+               'review_request_id': {
+                   'type': int,
+                   'description': 'The ID of the review request',
+               },
+           },
+       )
+       def create(self, request, review_request_id, *args, **kwargs):
+           try:
+               review_request = review_request_resource.get_object(
+                   request, review_request_id, *args, **kwargs)
+           except ObjectDoesNotExist:
+               return DOES_NOT_EXIST
+
+           new_review = Review.objects.create(
+               review_request=review_request,
+               user=request.user,
+               body_top='Sample review body')
+           new_review.publish(user=request.user)
+
+           return 201, {
+               self.item_result_key: new_review
+           }
+
+   sample_review_resource = SampleExtensionResource()
+
+
+The extension would then make use of this with:
+
+.. code-block:: python
+
+   class SampleExtension(Extension):
+       resources = [sample_review_resource]
+
+
+With this, one would be able to POST to this resource to create reviews that
+contained the text "Sample review body". This API endpoint would be registered
+at ``/api/extensions/sample_extension.extension.SampleExtension/reviews/``.
