diff --git a/contrib/tools/generate_extension.py b/contrib/tools/generate_extension.py
index 2fc7fb688f002db2df963ea635f063763281ec08..4a431243c04f7a376eb2515593ccf12cfebe752d 100755
--- a/contrib/tools/generate_extension.py
+++ b/contrib/tools/generate_extension.py
@@ -131,11 +131,6 @@ def parse_options():
     parser.add_option("--author",
                       dest="author", default=None,
                       help="author of the extension")
-    parser.add_option("--dashboard-link",
-                      dest="dashboard_link", default=None,
-                      metavar="DASHBOARD_LINK_LABEL",
-                      help="creates a dashboard link with this name in the " \
-                           "review requests sidebar (optional)")
     parser.add_option("--is-configurable",
                       dest="is_configurable", action="store_true",
                       default=False,
@@ -230,15 +225,6 @@ def main():
     builder.add_template("extension/admin_urls.py",
                          "{{PACKAGE}}/admin_urls.py")
 
-    if options.dashboard_link is not None:
-        builder.add_template("extension/urls.py",
-                             "{{PACKAGE}}/urls.py")
-        builder.add_template("extension/templates/extension/dashboard.html",
-                             "{{PACKAGE}}/templates/{{PACKAGE}}/dashboard.html"
-                             )
-        builder.add_template("extension/views.py",
-                             "{{PACKAGE}}/views.py")
-
     if options.is_configurable:
         builder.add_template("extension/templates/extension/configure.html",
                              "{{PACKAGE}}/templates/{{PACKAGE}}/configure.html"
diff --git a/contrib/tools/templates/extensions/extension/extension.py b/contrib/tools/templates/extensions/extension/extension.py
index 51c251d933e7032b15a2b9609950525afa46e848..b3163d7382ad25e0353b542a646effc4116b1c5d 100644
--- a/contrib/tools/templates/extensions/extension/extension.py
+++ b/contrib/tools/templates/extensions/extension/extension.py
@@ -5,28 +5,6 @@ from __future__ import unicode_literals
 from django.conf import settings
 from django.conf.urls.defaults import patterns, include
 from reviewboard.extensions.base import Extension
-{%- if dashboard_link is not none %}
-from reviewboard.extensions.hooks import DashboardHook, URLHook
-{% endif %}
-
-
-{%- if dashboard_link is not none %}
-class {{class_name}}URLHook(URLHook):
-    def __init__(self, extension, *args, **kwargs):
-        pattern = patterns('', (r'^{{package_name}}/',
-                            include('{{package_name}}.urls')))
-        super({{class_name}}URLHook, self).__init__(extension, pattern)
-
-
-class {{class_name}}DashboardHook(DashboardHook):
-    def __init__(self, extension, *args, **kwargs):
-        entries = [{
-            'label': '{{dashboard_link}}',
-            'url': settings.SITE_ROOT + '{{package_name}}/',
-        }]
-        super({{class_name}}DashboardHook, self).__init__(extension,
-                entries=entries, *args, **kwargs)
-{%- endif %}
 
 
 class {{class_name}}(Extension):
@@ -41,9 +19,4 @@ class {{class_name}}(Extension):
 
     def initialize(self):
         # Your extension initialization is done here.
-{%- if dashboard_link is not none %}
-        self.url_hook = {{class_name}}URLHook(self)
-        self.dashboard_hook = {{class_name}}DashboardHook(self)
-{%- else %}
         pass
-{%- endif %}
diff --git a/contrib/tools/templates/extensions/extension/urls.py b/contrib/tools/templates/extensions/extension/urls.py
deleted file mode 100644
index 9fcf15d75add844599a03de5eb341d1a99eaf94d..0000000000000000000000000000000000000000
--- a/contrib/tools/templates/extensions/extension/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import unicode_literals
-
-from django.conf.urls.defaults import patterns, url
-
-
-urlpatterns = patterns('{{package_name}}.views',
-    url(r'^$', 'dashboard'),
-)
diff --git a/contrib/tools/templates/extensions/extension/views.py b/contrib/tools/templates/extensions/extension/views.py
index 3c7a8793848ac925c54d475a1bd333f29582f84e..8440e25e787fc9be264b5509b64cc8c0e41c3db7 100644
--- a/contrib/tools/templates/extensions/extension/views.py
+++ b/contrib/tools/templates/extensions/extension/views.py
@@ -8,10 +8,3 @@ from django.template.context import RequestContext
 def configure(request, template_name="{{package_name}}/configure.html"):
     return render_to_response(template_name, RequestContext(request))
 {%- endif %}
-
-
-{%- if dashboard_link is not none %}
-def dashboard(request, template_name='{{package_name}}/dashboard.html'):
-    return render_to_response(template_name, RequestContext(request))
-{%- endif %}
-
diff --git a/docs/manual/extending/extensions/hooks/dashboard-hook.rst b/docs/manual/extending/extensions/hooks/dashboard-hook.rst
deleted file mode 100644
index 4ccda367e5ecab957981222e2b99cb86a66fc8ba..0000000000000000000000000000000000000000
--- a/docs/manual/extending/extensions/hooks/dashboard-hook.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-.. _dashboard-hook:
-
-=============
-DashboardHook
-=============
-
-:py:class:`reviewboard.extensions.hooks.DashboardHook` can be used to define a
-custom dashboard page for your Extension. :py:class:`DashboardHook` requires
-two arguments for initialization: the extension instance and a list of entries.
-Each entry in this list must be a dictionary with the following keys:
-
-* **label**: Label to appear on the dashboard's navigation pane.
-* **url**: URL for the dashboard page.
-
-If the extension needs only one dashboard, then it needs only one entry in
-this list. (See :ref:`extension-navigation-bar-hook`)
-
-
-Example
-=======
-
-.. code-block:: python
-
-    from reviewboard.extensions.base import Extension
-    from reviewboard.extensions.hooks import DashboardHook
-    from reviewboard.site.urlresolvers import local_site_reverse
-
-
-    class SampleExtension(Extension):
-        def initialize(self):
-            DashboardHook(
-                self,
-                entries=[
-                    {
-                        'label': 'My Label',
-                        'url': local_site_reverse('my-page-url-name'),
-                    }
-                ]
-            )
-
-
-Corresponding code in :file:`views.py`:
-
-.. code-block:: python
-
-    def dashboard(request, template_name='sample_extension/dashboard.html'):
-        return render_to_response(template_name, RequestContext(request))
-
-
-Corresponding template :file:`dashboard.html`:
-
-.. code-block:: html+django
-
-    {% extends "base.html" %}
-    {% load djblets_deco i18n %}
-
-    {% block title %}{% trans "Sample Extension Dashboard" %}{% endblock %}
-
-    {% block content %}
-    {%  box "reports" %}
-     <h1 class="title">{% trans "Sample Extension Dashboard" %}</h1>
-
-     <div class="main">
-      <p>{% trans "This is my new Dashboard page for Review Board" %}</p>
-     </div>
-    {%  endbox %}
-    {% endblock %}
diff --git a/docs/manual/extending/extensions/hooks/dashboard-sidebar-items-hook.rst b/docs/manual/extending/extensions/hooks/dashboard-sidebar-items-hook.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d16a1cec024a563b47b0116ba6954d3f80929f8a
--- /dev/null
+++ b/docs/manual/extending/extensions/hooks/dashboard-sidebar-items-hook.rst
@@ -0,0 +1,48 @@
+.. _dashboard-sidebar-items-hook:
+
+=========================
+DashboardSidebarItemsHook
+=========================
+
+:py:class:`reviewboard.extensions.hooks.DashboardSidebarItemsHook` can be used
+to add items to the sidebar on the dashboard. These items can contain
+anything: Links, images, statistics, or anything else the extension may want
+to provide.
+
+Sidebar items are subclasses of
+:py:class:`reviewboard.datagrids.sidebar.BaseSidebarItem`. Review Board
+provides two built-in items:
+:py:class:`reviewboard.datagrids.sidebar.BaseSidebarSection` and
+:py:class:`reviewboard.datagrids.sidebar.SidebarNavItem`.
+
+To use the hook, simply instantiate it and pass a list of BaseSidebarItem
+subclasses to it. These will then automatically appear in the dashboard.
+
+
+Example
+=======
+
+.. code-block:: python
+
+    from reviewboard.datagrids.sidebar import (BaseSidebarSection,
+                                               SidebarNavItem)
+    from reviewboard.extensions.base import Extension
+    from reviewboard.extensions.hooks import DashboardSidebarItemsHook
+
+
+    class SampleSidebarSection(BaseSidebarSection):
+        label = 'My Links'
+
+        def get_items(self):
+            return [
+                SidebarNavItem(label='Link 1',
+                               url_name='myvendor_url_name_1',
+                               count=10),
+                SidebarNavItem(label='Link 2',
+                               url_name='myvendor_url_name_2')
+            ]
+
+
+    class SampleExtension(Extension):
+        def initialize(self):
+            DashboardSidebarItemsHook(self, [SampleSidebarSection])
diff --git a/docs/manual/extending/extensions/hooks/index.rst b/docs/manual/extending/extensions/hooks/index.rst
index fdfea33ff0e1067308c5a913dd8fc0408b7d2b04..ba2c918abaf0f7f9d4676c999674041fea2848a9 100644
--- a/docs/manual/extending/extensions/hooks/index.rst
+++ b/docs/manual/extending/extensions/hooks/index.rst
@@ -20,7 +20,7 @@ The following hooks are available for use by extensions.
    account-page-forms-hook
    action-hooks
    comment-detail-display-hook
-   dashboard-hook
+   dashboard-sidebar-items-hook
    dashboard-columns-hook
    datagrid-columns-hook
    file-attachment-thumbnail-hook
@@ -30,4 +30,4 @@ The following hooks are available for use by extensions.
    review-ui-hook
    template-hook
    url-hook
-   user-page-sidebar-hook
+   user-page-sidebar-items-hook
diff --git a/docs/manual/extending/extensions/hooks/user-page-sidebar-hook.rst b/docs/manual/extending/extensions/hooks/user-page-sidebar-hook.rst
deleted file mode 100644
index 1236daf641fec617a12906d9524a1bb5cd32653a..0000000000000000000000000000000000000000
--- a/docs/manual/extending/extensions/hooks/user-page-sidebar-hook.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-.. _user-page-sidebar-hook:
-
-===================
-UserPageSidebarHook
-===================
-
-:py:class:`reviewboard.extensions.hooks.UserPageSidebarHook` can be used to
-introduce additional items in the user page. :py:class:`UserPageSidebarHook`
-requires two arguments for initialization: the extension instance and a list
-of entries. Each entry in this list must be a dictionary with the following
-keys:
-
-   * **label**: Label to appear on the UserPage navigation pane.
-   * **url**: URL for the UserPage Entry.
-
-The dictionary can also have an optional **subitems** key to show additional
-items under a main label. Each entry of the subitems must be a dictionary with
-the following keys:
-
-   * **label**: Sub-Item to appear on the UserPage navigation pane.
-   * **url**: URL for the Sub-Item
-
-
-Example
-=======
-
-.. code-block:: python
-
-    from django.conf import settings
-    from reviewboard.extensions.base import Extension
-    from reviewboard.extensions.hooks import UserPageSidebarHook
-
-
-    class SampleExtension(Extension):
-        def initialize(self):
-            UserPageSidebarHook(
-                self,
-                entries = [
-                    {
-                        'label': 'A SampleExtension Label',
-                        'url': settings.SITE_ROOT + 'sample_extension/',
-                    }
-                ]
-            )
-
-
-If you want to include sub-items in the sidebar::
-
-    from django.conf import settings
-    from reviewboard.extensions.base import Extension
-    from reviewboard.extensions.hooks import UserPageSidebarHook
-
-
-    class SampleExtension(Extension):
-        def initialize(self):
-            UserPageSidebarHook(
-                self,
-                entries = [
-                    {
-                        'label': 'User Menu with SubItems',
-                        'url': settings.SITE_ROOT + 'sample_extension/',
-                        'subitems': [
-                            {
-                                'label': 'SubItem entry',
-                                'url': settings.SITE_ROOT + 'subitem/',
-                            },
-                            {
-                                'label': 'Another SubItem entry',
-                                'url': settings.SITE_ROOT + 'subitem2/',
-                            }
-                        ]
-                    }
-                ]
-            )
diff --git a/docs/manual/extending/extensions/hooks/user-page-sidebar-items-hook.rst b/docs/manual/extending/extensions/hooks/user-page-sidebar-items-hook.rst
new file mode 100644
index 0000000000000000000000000000000000000000..eaca941056854dc1a4a4d9b8afb520abbcf3cbac
--- /dev/null
+++ b/docs/manual/extending/extensions/hooks/user-page-sidebar-items-hook.rst
@@ -0,0 +1,49 @@
+.. _user-page-sidebar-items-hook:
+
+========================
+UserPageSidebarItemsHook
+========================
+
+:py:class:`reviewboard.extensions.hooks.UserPageSidebarItemsHook` can be used
+to add items to the sidebar on the user page. These items can contain
+anything: Links, images, statistics, or anything else the extension may want
+to provide.
+
+Sidebar items are subclasses of
+:py:class:`reviewboard.datagrids.sidebar.BaseSidebarItem`. Review Board
+provides two built-in items:
+:py:class:`reviewboard.datagrids.sidebar.BaseSidebarSection` and
+:py:class:`reviewboard.datagrids.sidebar.SidebarNavItem`.
+
+To use the hook, simply instantiate it and pass a list of BaseSidebarItem
+subclasses to it. These will then automatically appear in the user page's
+sidebar.
+
+
+Example
+=======
+
+.. code-block:: python
+
+    from reviewboard.datagrids.sidebar import (BaseSidebarSection,
+                                               SidebarNavItem)
+    from reviewboard.extensions.base import Extension
+    from reviewboard.extensions.hooks import UserPageSidebarItemsHook
+
+
+    class SampleSidebarSection(BaseSidebarSection):
+        label = 'My Links'
+
+        def get_items(self):
+            return [
+                SidebarNavItem(label='Link 1',
+                               url_name='myvendor_url_name_1',
+                               count=10),
+                SidebarNavItem(label='Link 2',
+                               url_name='myvendor_url_name_2')
+            ]
+
+
+    class SampleExtension(Extension):
+        def initialize(self):
+            UserPageSidebarItemsHook(self, [SampleSidebarSection])
diff --git a/reviewboard/datagrids/views.py b/reviewboard/datagrids/views.py
index f72de60d80a6d9827c6bfcc5376490e0b6130acd..5104e7dd4eff2be8bbad9b9f5e8a565b02897d33 100644
--- a/reviewboard/datagrids/views.py
+++ b/reviewboard/datagrids/views.py
@@ -8,7 +8,6 @@ from django.utils.translation import ugettext_lazy as _
 
 from reviewboard.accounts.decorators import (check_login_required,
                                              valid_prefs_required)
-from reviewboard.extensions.hooks import DashboardHook, UserPageSidebarHook
 from reviewboard.datagrids.grids import (DashboardDataGrid,
                                          GroupDataGrid,
                                          ReviewRequestDataGrid,
diff --git a/reviewboard/extensions/hooks.py b/reviewboard/extensions/hooks.py
index 4fc09b748a40e50938a37f218386034072e9c78e..027f8ad37f518f41677ea8036cefda8586db377d 100644
--- a/reviewboard/extensions/hooks.py
+++ b/reviewboard/extensions/hooks.py
@@ -10,7 +10,7 @@ from reviewboard.accounts.pages import (get_page_class,
                                         unregister_account_page_class)
 from reviewboard.attachments.mimetypes import (register_mimetype_handler,
                                                unregister_mimetype_handler)
-from reviewboard.datagrids.grids import DashboardDataGrid
+from reviewboard.datagrids.grids import DashboardDataGrid, UserPageDataGrid
 from reviewboard.reviews.fields import (get_review_request_fieldset,
                                         register_review_request_fieldset,
                                         unregister_review_request_fieldset)
@@ -86,10 +86,34 @@ class AccountPageFormsHook(ExtensionHook):
 
 
 @six.add_metaclass(ExtensionHookPoint)
-class DashboardHook(ExtensionHook):
-    def __init__(self, extension, entries=[], *args, **kwargs):
-        super(DashboardHook, self).__init__(extension, *args, **kwargs)
-        self.entries = entries
+class DataGridSidebarItemsHook(ExtensionHook):
+    """A hook for adding items to the sidebar of a datagrid.
+
+    Extensions can use this hook to plug new items into the sidebar of
+    any datagrid supporting sidebars.
+
+    The items can be any subclass of
+    :py:class:`reviewboard.datagrids.sidebar.BaseSidebarItem`, including the
+    built-in :py:class:`reviewboard.datagrids.sidebar.BaseSidebarSection` and
+    built-in :py:class:`reviewboard.datagrids.sidebar.SidebarNavItem`.
+    """
+    def __init__(self, extension, datagrid, item_classes):
+        super(DataGridSidebarItemsHook, self).__init__(extension)
+
+        if not hasattr(datagrid, 'sidebar'):
+            raise ValueError('The datagrid provided does not have a sidebar')
+
+        self.datagrid = datagrid
+        self.item_classes = item_classes
+
+        for item in item_classes:
+            datagrid.sidebar.add_item(item)
+
+    def shutdown(self):
+        super(DataGridSidebaritem_classesHook, self).shutdown()
+
+        for item in self.item_classes:
+            self.datagrid.sidebar.remove_item(item)
 
 
 # We don't use the ExtensionHookPoint metaclass here, because we actually
@@ -114,25 +138,20 @@ class DashboardColumnsHook(DataGridColumnsHook):
 
 
 @six.add_metaclass(ExtensionHookPoint)
-class UserPageSidebarHook(ExtensionHook):
-    """A Hook for adding entries to sidebar of /users/<user> page.
+class DashboardSidebarItemsHook(DataGridSidebarItemsHook):
+    """A hook for adding items to the sidebar of the dashboard.
 
-    This takes a list of entries. Each entry represents something on the
-    user page and is a dictionary with the following keys:
+    Extensions can use this hook to plug new items into the sidebar of
+    the dashboard. These will appear below the built-in items.
 
-        * ``label``:    The label to display.
-        * ``url``:      The URL to point to.
-        * ``subitems``: Dictionary for storing second level entries.
-
-    ``subitems`` is another dictionary that will be indented to show a
-    hierarchy of items. Each subitem is a dictionary with the following keys:
-
-        * ``label``:    The label for the sub-entry.
-        * ``url``:      The URL that the sub-entry points to.
+    The items can be any subclass of
+    :py:class:`reviewboard.datagrids.sidebar.BaseSidebarItem`, including the
+    built-in :py:class:`reviewboard.datagrids.sidebar.BaseSidebarSection` and
+    built-in :py:class:`reviewboard.datagrids.sidebar.SidebarNavItem`.
     """
-    def __init__(self, extension, entries=[], *args, **kwargs):
-        super(UserPageSidebarHook, self).__init__(extension)
-        self.entries = entries
+    def __init__(self, extension, item_classes):
+        super(DashboardSidebarItemsHook, self).__init__(
+            extension, DashboardDataGrid, item_classes)
 
 
 @six.add_metaclass(ExtensionHookPoint)
@@ -383,3 +402,20 @@ class HeaderActionHook(ActionHook):
 @six.add_metaclass(ExtensionHookPoint)
 class HeaderDropdownActionHook(ActionHook):
     """A hook for putting multiple actions into a header dropdown."""
+
+
+@six.add_metaclass(ExtensionHookPoint)
+class UserPageSidebarItemsHook(DataGridSidebarItemsHook):
+    """A hook for adding items to the sidebar of the user page.
+
+    Extensions can use this hook to plug new items into the sidebar of
+    the user page. These will appear below the built-in items.
+
+    The items can be any subclass of
+    :py:class:`reviewboard.datagrids.sidebar.BaseSidebarItem`, including the
+    built-in :py:class:`reviewboard.datagrids.sidebar.BaseSidebarSection` and
+    built-in :py:class:`reviewboard.datagrids.sidebar.SidebarNavItem`.
+    """
+    def __init__(self, extension, item_classes):
+        super(UserPageSidebarItemsHook, self).__init__(
+            extension, UserPageDataGrid, item_classes)
diff --git a/reviewboard/extensions/tests.py b/reviewboard/extensions/tests.py
index 70b4d57745501129160b7cc78f09b3e4248b5d16..dd7483d993ce7e6a99f0999a3e9ba3811f81de9b 100644
--- a/reviewboard/extensions/tests.py
+++ b/reviewboard/extensions/tests.py
@@ -5,13 +5,12 @@ from djblets.extensions.manager import ExtensionManager
 from djblets.extensions.models import RegisteredExtension
 
 from reviewboard.extensions.base import Extension
-from reviewboard.extensions.hooks import (DashboardHook, DiffViewerActionHook,
+from reviewboard.extensions.hooks import (DiffViewerActionHook,
                                           HeaderActionHook,
                                           HeaderDropdownActionHook,
                                           NavigationBarHook,
                                           ReviewRequestActionHook,
-                                          ReviewRequestDropdownActionHook,
-                                          UserPageSidebarHook)
+                                          ReviewRequestDropdownActionHook)
 from reviewboard.testing.testcase import TestCase
 
 
@@ -32,70 +31,6 @@ class HookTests(TestCase):
 
         self.extension.shutdown()
 
-    def test_dashboard_hook(self):
-        """Testing dashboard sidebar extension hooks"""
-        entry = {
-            'label': 'My Hook',
-            'url': 'foo-url',
-        }
-
-        hook = DashboardHook(extension=self.extension, entries=[entry])
-        context = Context({
-            'dashboard_hook': hook,
-        })
-
-        entries = hook.entries
-        self.assertEqual(len(entries), 1)
-        self.assertEqual(entries[0], entry)
-
-        t = Template(
-            "{% load rb_extensions %}"
-            "{% for hook in dashboard_hook.entries %}"
-            "{{hook.label}} - {{hook.url}}"
-            "{% endfor %}")
-
-        self.assertEqual(t.render(context).strip(),
-                         '%(label)s - %(url)s' % entry)
-
-    def test_userpage_hook(self):
-        """Testing UserPage sidebar extension hooks"""
-        subentry = {
-            'label': 'Sub Hook',
-            'url': 'sub-foo-url'
-        }
-        entry = {
-            'label': 'User Hook',
-            'url': 'foo-url',
-            'subitems': [subentry]
-        }
-
-        hook = UserPageSidebarHook(extension=self.extension, entries=[entry])
-        context = Context({
-            'userpage_hook': hook,
-        })
-
-        entries = hook.entries
-        self.assertEqual(len(entries), 1)
-        self.assertEqual(entries[0], entry)
-        self.assertEqual(len(entries[0]['subitems']), 1)
-        self.assertEqual(entries[0]['subitems'][0], subentry)
-
-        t = Template(
-            "{% load rb_extensions %}"
-            "{% for hook in userpage_hook.entries %}"
-            "{{hook.label}} - {{hook.url}}"
-            "{% for subhook in hook.subitems %}"
-            " -- {{subhook.label}} - {{subhook.url}}"
-            "{% endfor %}"
-            "{% endfor %}")
-
-        self.assertEqual(t.render(context).strip(),
-                         '%s - %s -- %s - %s' % (
-                         entry['label'],
-                         entry['url'],
-                         entry['subitems'][0]['label'],
-                         entry['subitems'][0]['url']))
-
     def test_diffviewer_action_hook(self):
         """Testing diff viewer action extension hooks"""
         self._test_action_hook('diffviewer_action_hooks', DiffViewerActionHook)
