diff --git a/js/src/admin/components/AdminNav.js b/js/src/admin/components/AdminNav.js
index ecf96ec83..8ee4b9699 100644
--- a/js/src/admin/components/AdminNav.js
+++ b/js/src/admin/components/AdminNav.js
@@ -21,6 +21,34 @@ export default class AdminNav extends Component {
     );
   }
 
+  oncreate(vnode) {
+    super.oncreate(vnode);
+
+    this.scrollToActive();
+  }
+
+  onupdate() {
+    this.scrollToActive();
+  }
+
+  scrollToActive() {
+    const children = $('.Dropdown-menu').children('.active');
+    const nav = $('#admin-navigation');
+    const time = app.previous.type ? 250 : 0;
+
+    if (
+      children.length > 0 &&
+      (children[0].offsetTop > nav.scrollTop() + nav.outerHeight() || children[0].offsetTop + children[0].offsetHeight < nav.scrollTop())
+    ) {
+      nav.animate(
+        {
+          scrollTop: children[0].offsetTop - nav.height() / 2,
+        },
+        time
+      );
+    }
+  }
+
   /**
    * Build an item list of main links to show in the admin navigation.
    *
@@ -29,6 +57,8 @@ export default class AdminNav extends Component {
   items() {
     const items = new ItemList();
 
+    items.add('category-core', <h4 className="ExtensionListTitle">{app.translator.trans('core.admin.nav.categories.core')}</h4>);
+
     items.add(
       'dashboard',
       <LinkButton href={app.route('dashboard')} icon="far fa-chart-bar" title={app.translator.trans('core.admin.nav.dashboard_title')}>
@@ -88,7 +118,7 @@ export default class AdminNav extends Component {
     Object.keys(categorizedExtensions).map((category) => {
       if (!this.query()) {
         items.add(
-          category,
+          `category-${category}`,
           <h4 className="ExtensionListTitle">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>,
           categories[category]
         );
@@ -100,7 +130,7 @@ export default class AdminNav extends Component {
 
         if (!query || title.toUpperCase().includes(query) || extension.description.toUpperCase().includes(query)) {
           items.add(
-            extension.id,
+            `extension-${extension.id}`,
             <ExtensionLinkButton
               href={app.route('extension', { id: extension.id })}
               extensionId={extension.id}
diff --git a/js/src/admin/components/ExtensionPage.js b/js/src/admin/components/ExtensionPage.js
index b428bed39..756031fdf 100644
--- a/js/src/admin/components/ExtensionPage.js
+++ b/js/src/admin/components/ExtensionPage.js
@@ -12,7 +12,6 @@ import Stream from '../../common/utils/Stream';
 import LoadingModal from './LoadingModal';
 import ExtensionPermissionGrid from './ExtensionPermissionGrid';
 import saveSettings from '../utils/saveSettings';
-import ExtensionData from '../utils/ExtensionData';
 import isExtensionEnabled from '../utils/isExtensionEnabled';
 
 export default class ExtensionPage extends Page {
@@ -30,6 +29,7 @@ export default class ExtensionPage extends Page {
       support: 'fas fa-life-ring',
       website: 'fas fa-link',
       donate: 'fas fa-donate',
+      source: 'fas fa-code',
     };
 
     // Backwards compatibility layer will be removed in
@@ -49,7 +49,7 @@ export default class ExtensionPage extends Page {
         {this.header()}
         {!this.isEnabled() ? (
           <div className="container">
-            <h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.enable_to_see')}</h2>
+            <h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.enable_to_see')}</h3>
           </div>
         ) : (
           <div className="ExtensionPage-body">{this.sections().toArray()}</div>
@@ -105,7 +105,7 @@ export default class ExtensionPage extends Page {
           {app.extensionData.extensionHasPermissions(this.extension.id) ? (
             ExtensionPermissionGrid.component({ extensionId: this.extension.id })
           ) : (
-            <h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h2>
+            <h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_permissions')}</h3>
           )}
         </div>
       </div>,
@@ -130,7 +130,7 @@ export default class ExtensionPage extends Page {
               <div className="Form-group">{this.submitButton()}</div>
             </div>
           ) : (
-            <h2 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h2>
+            <h3 className="ExtensionPage-subHeader">{app.translator.trans('core.admin.extension.no_settings')}</h3>
           )}
         </div>
       </div>
@@ -170,17 +170,15 @@ export default class ExtensionPage extends Page {
   infoItems() {
     const items = new ItemList();
 
-    if (this.extension.authors) {
+    const links = this.extension.links;
+
+    if (links.authors.length) {
       let authors = [];
 
-      Object.keys(this.extension.authors).map((author, i) => {
-        const link = this.extension.authors[author].homepage
-          ? this.extension.authors[author].homepage
-          : 'mailto:' + this.extension.authors[author].email;
-
+      links.authors.map((author) => {
         authors.push(
-          <Link href={link} external={true} target="_blank">
-            {this.extension.authors[author].name}
+          <Link href={author.link} external={true} target="_blank">
+            {author.name}
           </Link>
         );
       });
@@ -188,35 +186,17 @@ export default class ExtensionPage extends Page {
       items.add('authors', [icon('fas fa-user'), <span>{punctuateSeries(authors)}</span>]);
     }
 
-    const infoData = {};
-
-    if (this.extension.source || this.extension.support) {
-      infoData.source = {
-        icon: 'fas fa-code',
-        href: this.extension.source ? this.extension.source.url : this.extension.support.source,
-      };
-    }
-
     Object.keys(this.infoFields).map((field) => {
-      const info = this.extension.extra['flarum-extension'].info;
-
-      if (info && info[field]) {
-        infoData[field] = {
-          icon: this.infoFields[field],
-          href: info[field],
-        };
+      if (links[field]) {
+        items.add(
+          field,
+          <LinkButton href={links[field]} icon={this.infoFields[field]} external={true} target="_blank">
+            {app.translator.trans(`core.admin.extension.info_links.${field}`)}
+          </LinkButton>
+        );
       }
     });
 
-    Object.entries(infoData).map(([field, value]) => {
-      items.add(
-        field,
-        <LinkButton href={value.href} icon={value.icon} external={true} target="_blank">
-          {app.translator.trans(`core.admin.extension.info_links.${field}`)}
-        </LinkButton>
-      );
-    });
-
     return items;
   }
 
@@ -233,6 +213,9 @@ export default class ExtensionPage extends Page {
    * Depending on the type of input, you can set the type to 'bool', 'select', or
    * any standard <input> type.
    *
+   * Alternatively, you can pass a callback that will be executed in ExtensionPage's
+   * context to include custom JSX elements.
+   *
    * @example
    *
    * {
@@ -258,6 +241,10 @@ export default class ExtensionPage extends Page {
    * @returns {JSX.Element}
    */
   buildSettingComponent(entry) {
+    if (typeof entry === 'function') {
+      return entry.call(this);
+    }
+
     const setting = entry.setting;
     const value = this.setting([setting])();
     if (['bool', 'checkbox', 'switch', 'boolean'].includes(entry.type)) {
diff --git a/js/src/admin/components/ExtensionsWidget.js b/js/src/admin/components/ExtensionsWidget.js
index 626fbe54d..f8a28d51d 100644
--- a/js/src/admin/components/ExtensionsWidget.js
+++ b/js/src/admin/components/ExtensionsWidget.js
@@ -15,33 +15,31 @@ export default class ExtensionsWidget extends DashboardWidget {
 
     return (
       <div className="ExtensionsWidget-list">
-        <div className="container">
-          {Object.keys(categories).map((category) => {
-            if (categorizedExtensions[category]) {
-              return (
-                <div className="ExtensionList-Category">
-                  <h4 className="ExtensionList-Label">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>
-                  <ul className="ExtensionList">
-                    {categorizedExtensions[category].map((extension) => {
-                      return (
-                        <li className={'ExtensionListItem ' + (!isExtensionEnabled(extension.id) ? 'disabled' : '')}>
-                          <Link href={app.route('extension', { id: extension.id })}>
-                            <div className="ExtensionListItem-content">
-                              <span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
-                                {extension.icon ? icon(extension.icon.name) : ''}
-                              </span>
-                              <span className="ExtensionListItem-title">{extension.extra['flarum-extension'].title}</span>
-                            </div>
-                          </Link>
-                        </li>
-                      );
-                    })}
-                  </ul>
-                </div>
-              );
-            }
-          })}
-        </div>
+        {Object.keys(categories).map((category) => {
+          if (categorizedExtensions[category]) {
+            return (
+              <div className="ExtensionList-Category">
+                <h4 className="ExtensionList-Label">{app.translator.trans(`core.admin.nav.categories.${category}`)}</h4>
+                <ul className="ExtensionList">
+                  {categorizedExtensions[category].map((extension) => {
+                    return (
+                      <li className={'ExtensionListItem ' + (!isExtensionEnabled(extension.id) ? 'disabled' : '')}>
+                        <Link href={app.route('extension', { id: extension.id })}>
+                          <div className="ExtensionListItem-content">
+                            <span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
+                              {extension.icon ? icon(extension.icon.name) : ''}
+                            </span>
+                            <span className="ExtensionListItem-title">{extension.extra['flarum-extension'].title}</span>
+                          </div>
+                        </Link>
+                      </li>
+                    );
+                  })}
+                </ul>
+              </div>
+            );
+          }
+        })}
       </div>
     );
   }
diff --git a/js/src/admin/utils/ExtensionData.js b/js/src/admin/utils/ExtensionData.js
index 336a45f23..25f7a90a3 100644
--- a/js/src/admin/utils/ExtensionData.js
+++ b/js/src/admin/utils/ExtensionData.js
@@ -26,6 +26,8 @@ export default class ExtensionData {
   /**
    * This function registers your settings with Flarum
    *
+   * It takes either a settings object or a callback.
+   *
    * @example
    *
    * .registerSetting({
diff --git a/less/admin/AdminHeader.less b/less/admin/AdminHeader.less
index dd9c9414a..298105c6e 100644
--- a/less/admin/AdminHeader.less
+++ b/less/admin/AdminHeader.less
@@ -11,6 +11,7 @@
 
   .AdminHeader-description {
     margin: 0;
+    color: @control-color;
   }
 
   .icon {
diff --git a/less/admin/AdminNav.less b/less/admin/AdminNav.less
index 2a71e45ee..bafdbca7d 100644
--- a/less/admin/AdminNav.less
+++ b/less/admin/AdminNav.less
@@ -41,16 +41,13 @@
 }
 
 @media @tablet {
-  .item-search{
-    display: none;
-  }
-
-  .ExtensionItem, .item-search {
-    display: none !important;
-  }
-
-  .ExtensionListTitle {
-    display: none !important;
+  .AdminNav {
+    .item-search,
+    li[class^="item-category"],
+    li[class^="item-extension"],
+    .AdminLinkButton-description {
+      display: none !important;
+    }
   }
 }
 
@@ -80,7 +77,7 @@
 }
 
 
-@media @desktop, @desktop-hd {
+@media @desktop-up {
   .App-nav {
     position: absolute;
     top: @header-height;
@@ -107,36 +104,47 @@
         margin-bottom: 20px;
       }
 
+      .item-category-core {
+        > .ExtensionListTitle {
+          margin-top: 10px;
+        }
+      }
+
       > li {
         > a {
           padding: 10px 10px 10px 45px;
           display: block;
           text-decoration: none;
         }
+
         > a,
         > a:hover,
         &.active > a {
           color: @text-color;
         }
+
         > a:hover {
           background: @control-bg;
         }
+
         &.active > a {
-          background: @primary-color;
+          background: @control-color;
           font-weight: normal;
+          color: @body-bg;
 
           .Button-label,
           .Button-icon {
-            color: @body-bg;
             font-weight: bold;
           }
         }
+
         .Button-icon {
           float: left;
           font-size: 13px !important;
           margin-left: -25px !important;
           margin-top: 4px !important;
         }
+
         .Button-label {
           padding-left: 5px;
           font-size: 14px;
@@ -152,7 +160,7 @@
         .ExtensionListTitle {
           color: @muted-color;
           text-transform: uppercase;
-          margin: 25px 0 15px 15px;
+          margin: 25px 0 8px 15px;
         }
 
         .ExtensionIcon {
@@ -180,6 +188,11 @@
 
 }
 
+.AdminLinkButton-description {
+  white-space: normal;
+  padding-left: 5px;
+}
+
 .ExtensionListItem-Dot {
   height: 10px;
   width: 10px;
diff --git a/less/admin/DashboardPage.less b/less/admin/DashboardPage.less
index 57c623ba7..589b5f281 100644
--- a/less/admin/DashboardPage.less
+++ b/less/admin/DashboardPage.less
@@ -10,17 +10,21 @@
   border-radius: @border-radius;
   padding: 20px;
   margin-bottom: 20px;
+
+  .Button {
+    .Button--color(@control-color, @body-bg)
+  }
 }
 
 .StatusWidget {
   color: @muted-color;
 
-  > ul {
+  >ul {
     margin: 0;
     padding: 0;
     list-style-type: none;
 
-    > li {
+    >li {
       display: inline-block;
       margin-right: 30px;
       vertical-align: middle;
@@ -31,6 +35,7 @@
         overflow: hidden;
         text-overflow: ellipsis;
       }
+
       &.item-tools {
         float: right;
         margin-right: 0;
diff --git a/less/admin/ExtensionPage.less b/less/admin/ExtensionPage.less
index 5bb5cefb6..dc98c415d 100644
--- a/less/admin/ExtensionPage.less
+++ b/less/admin/ExtensionPage.less
@@ -119,6 +119,11 @@
     }
   }
 
+  .ExtensionPage-settings, .ExtensionPage-permissions {
+    .ExtensionPage-subHeader {
+      margin: 5px 0px;
+    }
+  }
 
   .ExtensionPage-settings {
     margin-top: 20px;
@@ -132,7 +137,6 @@
   .ExtensionPage-subHeader {
     color: @muted-color;
     font-weight: normal;
-    text-align: center;
   }
 
 
diff --git a/less/admin/ExtensionWidget.less b/less/admin/ExtensionWidget.less
index 689aec6ec..92437af56 100644
--- a/less/admin/ExtensionWidget.less
+++ b/less/admin/ExtensionWidget.less
@@ -4,77 +4,75 @@
 }
 
 .ExtensionsWidget-list {
-  > .container {
-    padding: 0;
-    background-color: @body-bg;
+  padding: 0;
+  background-color: @body-bg;
 
-    .ExtensionList-Category {
-      background: @control-bg;
-      padding: 20px 0 20px 20px;
-      margin-bottom: 20px;
-      border-radius: @border-radius;
+  .ExtensionList-Category {
+    background: @control-bg;
+    padding: 20px 0 20px 20px;
+    margin-bottom: 20px;
+    border-radius: @border-radius;
 
-      .ExtensionList-Label {
-        margin-top: 0;
-        color: @muted-color;
-      }
-    }
-
-    .ExtensionGroup {
-      margin-bottom: 20px;
-
-      h3 {
-        color: @muted-color;
-        text-transform: uppercase;
-        font-size: 12px;
-        margin: 0 0 10px;
-      }
-    }
-
-    .ExtensionList {
-      padding: 0;
-      list-style: none;
-      display: grid;
-      grid-gap: 10px;
-      grid-template-columns: repeat(auto-fit, 90px);
-      margin-bottom: 0;
-
-      > li {
-        text-align: left;
-        position: relative;
-        display: block;
-      }
-    }
-  }
-
-  .ExtensionListItem.disabled {
-    .ExtensionListItem-title {
-      opacity: 0.5;
+    .ExtensionList-Label {
+      margin-top: 0;
       color: @muted-color;
     }
+  }
 
-    .ExtensionListItem-icon {
-      opacity: 0.5;
+  .ExtensionGroup {
+    margin-bottom: 20px;
+
+    h3 {
+      color: @muted-color;
+      text-transform: uppercase;
+      font-size: 12px;
+      margin: 0 0 10px;
     }
   }
 
-  .ExtensionListItem {
-    transition: .15s ease-in-out;
+  .ExtensionList {
+    padding: 0;
+    list-style: none;
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: repeat(auto-fit, 90px);
+    margin-bottom: 0;
 
-    &:hover {
-      transform: scale(1.05);
-    }
-
-    .ExtensionListItem-title {
+    > li {
+      text-align: left;
+      position: relative;
       display: block;
-      text-align: center;
-      margin-top: 5px;
-      color: @text-color;
     }
+  }
+}
 
-    a:hover {
-      text-decoration: none;
-    }
+.ExtensionListItem.disabled {
+  .ExtensionListItem-title {
+    opacity: 0.5;
+    color: @muted-color;
+  }
+
+  .ExtensionListItem-icon {
+    opacity: 0.5;
+  }
+}
+
+.ExtensionListItem {
+  transition: .15s ease-in-out;
+
+  &:hover {
+    transform: scale(1.05);
+  }
+
+  .ExtensionListItem-title {
+    display: block;
+    text-align: center;
+    margin-top: 5px;
+    color: @text-color;
+  }
+
+  a:hover {
+    text-decoration: none;
   }
 }
 
diff --git a/less/admin/PermissionsPage.less b/less/admin/PermissionsPage.less
index ac7b01135..50b72ed34 100644
--- a/less/admin/PermissionsPage.less
+++ b/less/admin/PermissionsPage.less
@@ -5,7 +5,11 @@
   display: block;
   margin-left: 30px;
   overflow-x: auto;
-  padding: 8px 0 8px;
+  padding: 10px 0 8px;
+
+  > .container {
+    padding: 0 10px;
+  }
 }
 .Group {
   width: 90px;
@@ -42,6 +46,7 @@
 
 .PermissionGrid {
   white-space: nowrap;
+  padding-left: 0 12px;
 
   td, th {
     padding: 5px;
@@ -117,7 +122,7 @@
 }
 .PermissionGrid-section {
   td, th {
-    padding-top: 20px;
+    padding-top: 10px;
   }
 }
 .PermissionGrid-child {
diff --git a/src/Extension/Extension.php b/src/Extension/Extension.php
index ea7b90ed0..cdda23d67 100644
--- a/src/Extension/Extension.php
+++ b/src/Extension/Extension.php
@@ -346,6 +346,49 @@ class Extension implements Arrayable
         return null;
     }
 
+    /**
+     * Compile a list of links for this extension.
+     */
+    public function getLinks()
+    {
+        $links = [];
+
+        if (($sourceUrl = $this->composerJsonAttribute('source.url')) || ($sourceUrl = $this->composerJsonAttribute('support.source'))) {
+            $links['source'] = $sourceUrl;
+        }
+
+        if (($discussUrl = $this->composerJsonAttribute('support.forum'))) {
+            $links['discuss'] = $discussUrl;
+        }
+
+        if (($documentationUrl = $this->composerJsonAttribute('support.docs'))) {
+            $links['documentation'] = $documentationUrl;
+        }
+
+        if (($websiteUrl = $this->composerJsonAttribute('homepage'))) {
+            $links['website'] = $websiteUrl;
+        }
+
+        if (($supportEmail = $this->composerJsonAttribute('support.email'))) {
+            $links['support'] = "mailto:$supportEmail";
+        }
+
+        if (($funding = $this->composerJsonAttribute('funding')) && count($funding)) {
+            $links['donate'] = $funding[0]['url'];
+        }
+
+        $links['authors'] = [];
+
+        foreach ((array) $this->composerJsonAttribute('authors') as $author) {
+            $links['authors'][] = [
+                'name' => Arr::get($author, 'name'),
+                'link' => Arr::get($author, 'homepage') ?? (Arr::get($author, 'email') ? 'mailto:'.Arr::get($author, 'email') : ''),
+            ];
+        }
+
+        return array_merge($links, $this->composerJsonAttribute('flarum-extension.links') ?? []);
+    }
+
     /**
      * Tests whether the extension has assets.
      *
@@ -413,6 +456,7 @@ class Extension implements Arrayable
             'hasAssets'              => $this->hasAssets(),
             'hasMigrations'          => $this->hasMigrations(),
             'extensionDependencyIds' => $this->getExtensionDependencyIds(),
+            'links'                  => $this->getLinks(),
         ], $this->composerJson);
     }
 }