diff --git a/backend/src/baserow/api/v0/applications/serializers.py b/backend/src/baserow/api/v0/applications/serializers.py
index fe73d1b67..1f992c925 100644
--- a/backend/src/baserow/api/v0/applications/serializers.py
+++ b/backend/src/baserow/api/v0/applications/serializers.py
@@ -1,15 +1,17 @@
 from rest_framework import serializers
 
+from baserow.api.v0.groups.serializers import GroupSerializer
 from baserow.core.applications import registry
 from baserow.core.models import Application
 
 
 class ApplicationSerializer(serializers.ModelSerializer):
     type = serializers.SerializerMethodField()
+    group = GroupSerializer()
 
     class Meta:
         model = Application
-        fields = ('id', 'name', 'order', 'type')
+        fields = ('id', 'name', 'order', 'type', 'group')
         extra_kwargs = {
             'id': {
                 'read_only': True
diff --git a/backend/src/baserow/api/v0/applications/views.py b/backend/src/baserow/api/v0/applications/views.py
index f5fd73311..f44a76926 100644
--- a/backend/src/baserow/api/v0/applications/views.py
+++ b/backend/src/baserow/api/v0/applications/views.py
@@ -56,6 +56,15 @@ class ApplicationView(APIView):
     permission_classes = (IsAuthenticated,)
     core_handler = CoreHandler()
 
+    def get(self, request, application_id):
+        """Selects a single application and responds with a serialized version."""
+        application = get_object_or_404(
+            Application.objects.select_related('group'),
+            pk=application_id, group__users__in=[request.user]
+        )
+
+        return Response(ApplicationSerializer(application).data)
+
     @transaction.atomic
     @validate_body(ApplicationUpdateSerializer)
     @map_exceptions({
diff --git a/backend/tests/baserow/api/v0/applications/test_application_views.py b/backend/tests/baserow/api/v0/applications/test_application_views.py
index 7f0a8272d..e2ee16822 100644
--- a/backend/tests/baserow/api/v0/applications/test_application_views.py
+++ b/backend/tests/baserow/api/v0/applications/test_application_views.py
@@ -94,6 +94,46 @@ def test_create_application(api_client, data_fixture):
     assert response_json['order'] == database.order
 
 
+@pytest.mark.django_db
+def test_get_application(api_client, data_fixture):
+    user, token = data_fixture.create_user_and_token()
+    user_2, token_2 = data_fixture.create_user_and_token()
+    group = data_fixture.create_group(user=user)
+    group_2 = data_fixture.create_group(user=user_2)
+    application = data_fixture.create_database_application(group=group)
+    application_2 = data_fixture.create_database_application(group=group_2)
+
+    url = reverse('api_v0:applications:item',
+                  kwargs={'application_id': application_2.id})
+    response = api_client.get(
+        url,
+        format='json',
+        HTTP_AUTHORIZATION=f'JWT {token}'
+    )
+    assert response.status_code == 404
+
+    url = reverse('api_v0:applications:item',
+                  kwargs={'application_id': 99999})
+    response = api_client.get(
+        url,
+        format='json',
+        HTTP_AUTHORIZATION=f'JWT {token}'
+    )
+    assert response.status_code == 404
+
+    url = reverse('api_v0:applications:item',
+                  kwargs={'application_id': application.id})
+    response = api_client.get(
+        url,
+        format='json',
+        HTTP_AUTHORIZATION=f'JWT {token}'
+    )
+    response_json = response.json()
+    assert response.status_code == 200
+    assert response_json['id'] == application.id
+    assert response_json['group']['id'] == group.id
+
+
 @pytest.mark.django_db
 def test_update_application(api_client, data_fixture):
     user, token = data_fixture.create_user_and_token()
diff --git a/old-web-frontend/public/grid.html b/old-web-frontend/public/grid.html
index 297b16f6d..b00dfea57 100644
--- a/old-web-frontend/public/grid.html
+++ b/old-web-frontend/public/grid.html
@@ -57,7 +57,7 @@
                                         <i class="fas fa-ellipsis-v"></i>
                                     </a>
                                 </li>
-                                <li class="select-item">
+                                <li class="select-item select-item-loading">
                                     <a href="#" class="select-item-link">Group name 3</a>
                                     <a href="#" class="select-item-options">
                                         <i class="fas fa-ellipsis-v"></i>
@@ -102,7 +102,7 @@
                         </div>
                         <div class="sidebar-group-title">Group name 1</div>
                         <ul class="tree">
-                            <li class="tree-item">
+                            <li class="tree-item tree-item-loading">
                                 <div class="tree-action">
                                     <a href="#" class="tree-link">
                                         <i class="tree-type fas fa-database"></i>
@@ -139,7 +139,7 @@
                                 </div>
                             </li>
                             <li class="tree-item active">
-                                <div class="tree-action">
+                                <div class="tree-action tree-item-loading">
                                     <a href="#" class="tree-link">
                                         <i class="tree-type fas fa-database"></i>
                                         Webshop
@@ -169,7 +169,39 @@
                                     </li>
                                 </ul>
                             </li>
-                            <li class="tree-item">
+                            <li class="tree-item tree-item-loading active">
+                              <div class="tree-action tree-item-loading">
+                                <a href="#" class="tree-link">
+                                  <i class="tree-type fas fa-database"></i>
+                                  Webshop
+                                </a>
+                                <a href="#" class="tree-options">
+                                  <i class="fas fa-ellipsis-v"></i>
+                                </a>
+                              </div>
+                              <ul class="tree-subs">
+                                <li class="tree-sub active">
+                                  <a href="#" class="tree-sub-link">Customers</a>
+                                  <a href="#" class="tree-options">
+                                    <i class="fas fa-ellipsis-v"></i>
+                                  </a>
+                                </li>
+                                <li class="tree-sub">
+                                  <a href="#" class="tree-sub-link">Products very long name</a>
+                                  <a href="#" class="tree-options">
+                                    <i class="fas fa-ellipsis-v"></i>
+                                  </a>
+                                </li>
+                                <li class="tree-sub">
+                                  <a href="#" class="tree-sub-link">Categories</a>
+                                  <a href="#" class="tree-options">
+                                    <i class="fas fa-ellipsis-v"></i>
+                                  </a>
+                                </li>
+                              </ul>
+                            </li>
+
+                            <li class="tree-item tree-item-loading">
                                 <div class="tree-action">
                                     <a href="#" class="tree-link">
                                         <i class="tree-type fas fa-angle-down"></i>
@@ -188,7 +220,7 @@
                                             </a>
                                         </div>
                                     </li>
-                                    <li class="tree-item">
+                                    <li class="tree-item tree-item-loading">
                                         <div class="tree-action">
                                             <a href="#" class="tree-link">
                                                 <i class="tree-type fas fa-database"></i>
diff --git a/web-frontend/assets/scss/components/_select.scss b/web-frontend/assets/scss/components/_select.scss
index bdda6739e..e745f0ac3 100644
--- a/web-frontend/assets/scss/components/_select.scss
+++ b/web-frontend/assets/scss/components/_select.scss
@@ -63,10 +63,17 @@
     background-color: $color-neutral-100;
   }
 
+  &.select-item-loading::before {
+    content: " ";
+
+    @include loading(14px);
+    @include absolute(9px, 9px, auto, auto);
+  }
+
   &.active {
     background-color: $color-primary-100;
 
-    &::after {
+    &:not(.select-item-loading)::after {
       @extend .fas;
       @extend %select-item-size;
 
@@ -82,17 +89,6 @@
       display: none;
     }
   }
-
-  &.select-item-loading {
-    background-color: $color-neutral-100;
-
-    &::before {
-      content: " ";
-
-      @include loading(14px);
-      @include absolute(9px, 9px, auto, auto);
-    }
-  }
 }
 
 .select-item-link {
diff --git a/web-frontend/assets/scss/components/_sidebar.scss b/web-frontend/assets/scss/components/_sidebar.scss
index 830a9c80a..2945dd05f 100644
--- a/web-frontend/assets/scss/components/_sidebar.scss
+++ b/web-frontend/assets/scss/components/_sidebar.scss
@@ -50,3 +50,14 @@
   font-weight: 700;
   margin-bottom: 10px;
 }
+
+.sidebar-new {
+  font-size: 13px;
+  color: $color-neutral-300;
+  margin-left: 7px;
+
+  &:hover {
+    color: $color-neutral-500;
+    text-decoration: none;
+  }
+}
diff --git a/web-frontend/assets/scss/components/_tree.scss b/web-frontend/assets/scss/components/_tree.scss
index 542f6391d..63d193983 100644
--- a/web-frontend/assets/scss/components/_tree.scss
+++ b/web-frontend/assets/scss/components/_tree.scss
@@ -1,7 +1,7 @@
 .tree {
   list-style: none;
   padding: 0;
-  margin: 0;
+  margin: 0 0 12px;
 
   .tree-item & {
     padding-left: 8px;
@@ -18,6 +18,13 @@
   &.active {
     background-color: $color-primary-100;
   }
+
+  &.tree-item-loading::after {
+    content: " ";
+
+    @include loading(14px);
+    @include absolute(9px, 9px, auto, auto);
+  }
 }
 
 %tree-size {
@@ -122,6 +129,18 @@
   }
 }
 
+.tree-sub-add {
+  display: inline-block;
+  margin: 0 0 10px 10px;
+  font-size: 12px;
+  color: $color-neutral-300;
+
+  &:hover {
+    text-decoration: none;
+    color: $color-neutral-500;
+  }
+}
+
 .tree-options {
   display: none;
   position: absolute;
@@ -141,4 +160,8 @@
   :hover > & {
     display: block;
   }
+
+  .tree-item-loading > .tree-action > & {
+    display: none;
+  }
 }
diff --git a/web-frontend/components/Context.vue b/web-frontend/components/Context.vue
index 6fb984bba..651efb43b 100644
--- a/web-frontend/components/Context.vue
+++ b/web-frontend/components/Context.vue
@@ -14,8 +14,7 @@ export default {
   data() {
     return {
       open: false,
-      opener: null,
-      children: []
+      opener: null
     }
   },
   methods: {
@@ -81,9 +80,9 @@ export default {
           !isElement(this.opener, event.target) &&
           // If the click was not inside one of the context children of this context
           // menu.
-          !this.children.some(component =>
-            isElement(component.$el, event.target)
-          )
+          !this.moveToBody.children.some(child => {
+            return isElement(child.$el, event.target)
+          })
         ) {
           this.hide()
         }
@@ -96,6 +95,7 @@ export default {
     hide() {
       this.opener = null
       this.open = false
+      this.$emit('hidden')
 
       document.body.removeEventListener('click', this.$el.clickOutsideEvent)
     },
@@ -171,13 +171,6 @@ export default {
       }
 
       return positions
-    },
-    /**
-     * A child context can register itself with the parent to prevent closing of the
-     * parent when clicked inside the child.
-     */
-    registerContextChild(element) {
-      this.children.push(element)
     }
   }
 }
diff --git a/web-frontend/components/Modal.vue b/web-frontend/components/Modal.vue
index 67784f817..05c3d703c 100644
--- a/web-frontend/components/Modal.vue
+++ b/web-frontend/components/Modal.vue
@@ -1,19 +1,17 @@
 <template>
-  <transition name="fade">
-    <div
-      v-if="open"
-      ref="modalWrapper"
-      class="modal-wrapper"
-      @click="outside($event)"
-    >
-      <div class="modal-box">
-        <a class="modal-close" @click="hide()">
-          <i class="fas fa-times"></i>
-        </a>
-        <slot></slot>
-      </div>
+  <div
+    v-if="open"
+    ref="modalWrapper"
+    class="modal-wrapper"
+    @click="outside($event)"
+  >
+    <div class="modal-box">
+      <a class="modal-close" @click="hide()">
+        <i class="fas fa-times"></i>
+      </a>
+      <slot></slot>
     </div>
-  </transition>
+  </div>
 </template>
 
 <script>
@@ -56,7 +54,16 @@ export default {
      * Hide the modal.
      */
     hide() {
-      this.open = false
+      // This is a temporary fix. What happens is the model is opened by a context menu
+      // item and the user closes the modal, the element is first deleted and then the
+      // click outside event of the context is fired. It then checks if the click was
+      // inside one of his children, but because the modal element doesn't exists
+      // anymore it thinks it was outside, so is closes the context menu which we don't
+      // want automatically.
+      setTimeout(() => {
+        this.open = false
+      })
+      this.$emit('hidden')
       window.removeEventListener('keyup', this.keyup)
     },
     /**
diff --git a/web-frontend/components/group/GroupForm.vue b/web-frontend/components/group/GroupForm.vue
index ad46180e6..a5714eead 100644
--- a/web-frontend/components/group/GroupForm.vue
+++ b/web-frontend/components/group/GroupForm.vue
@@ -42,6 +42,9 @@ export default {
     values: {
       name: { required }
     }
+  },
+  mounted() {
+    this.$refs.name.focus()
   }
 }
 </script>
diff --git a/web-frontend/components/group/GroupsContext.vue b/web-frontend/components/group/GroupsContext.vue
index 6982a5a46..34fc261b8 100644
--- a/web-frontend/components/group/GroupsContext.vue
+++ b/web-frontend/components/group/GroupsContext.vue
@@ -1,5 +1,5 @@
 <template>
-  <Context class="select">
+  <Context ref="groupsContext" class="select">
     <div class="select-search">
       <i class="select-search-icon fas fa-search"></i>
       <input
@@ -13,28 +13,12 @@
       <div class="loading"></div>
     </div>
     <ul v-if="!isLoading && isLoaded && groups.length > 0" class="select-items">
-      <li
+      <GroupsContextItem
         v-for="group in searchAndSort(groups)"
         :key="group.id"
-        :ref="'groupSelect' + group.id"
-        class="select-item"
-      >
-        <div class="loading-overlay"></div>
-        <a class="select-item-link">
-          <Editable
-            :ref="'groupRename' + group.id"
-            :value="group.name"
-            @change="renameGroup(group, $event)"
-          ></Editable>
-        </a>
-        <a
-          :ref="'groupOptions' + group.id"
-          class="select-item-options"
-          @click="toggleContext(group.id)"
-        >
-          <i class="fas fa-ellipsis-v"></i>
-        </a>
-      </li>
+        :group="group"
+        @selected="hide"
+      ></GroupsContextItem>
     </ul>
     <div
       v-if="!isLoading && isLoaded && groups.length == 0"
@@ -42,22 +26,6 @@
     >
       No results found
     </div>
-    <Context ref="groupsItemContext">
-      <ul class="context-menu">
-        <li>
-          <a @click="toggleRename(contextId)">
-            <i class="context-menu-icon fas fa-fw fa-pen"></i>
-            Rename group
-          </a>
-        </li>
-        <li>
-          <a @click="deleteGroup(contextId)">
-            <i class="context-menu-icon fas fa-fw fa-trash"></i>
-            Delete group
-          </a>
-        </li>
-      </ul>
-    </Context>
     <div class="select-footer">
       <a class="select-footer-button" @click="$refs.createGroupModal.show()">
         <i class="fas fa-plus"></i>
@@ -72,18 +40,19 @@
 import { mapGetters, mapState } from 'vuex'
 
 import CreateGroupModal from '@/components/group/CreateGroupModal'
+import GroupsContextItem from '@/components/group/GroupsContextItem'
 import context from '@/mixins/context'
 
 export default {
-  name: 'GroupsItemContext',
+  name: 'GroupsContext',
   components: {
-    CreateGroupModal
+    CreateGroupModal,
+    GroupsContextItem
   },
   mixins: [context],
   data() {
     return {
-      query: '',
-      contextId: -1
+      query: ''
     }
   },
   computed: {
@@ -96,15 +65,14 @@ export default {
     })
   },
   methods: {
+    /**
+     * When the groups context select is opened for the for the first time we must make
+     * sure that all the groups are already loaded or going to be loaded.
+     */
     toggle(...args) {
       this.$store.dispatch('group/loadAll')
       this.getRootContext().toggle(...args)
     },
-    toggleContext(groupId) {
-      const target = this.$refs['groupOptions' + groupId][0]
-      this.contextId = groupId
-      this.$refs.groupsItemContext.toggle(target, 'bottom', 'right', 0)
-    },
     searchAndSort(groups) {
       const query = this.query
 
@@ -115,39 +83,6 @@ export default {
       // .sort((a, b) => {
       //   return a.order - b.order
       // })
-    },
-    toggleRename(id) {
-      this.$refs.groupsItemContext.hide()
-      this.$refs['groupRename' + id][0].edit()
-    },
-    renameGroup(group, event) {
-      const select = this.$refs['groupSelect' + group.id][0]
-      select.classList.add('select-item-loading')
-
-      this.$store
-        .dispatch('group/update', {
-          id: group.id,
-          values: {
-            name: event.value
-          }
-        })
-        .catch(() => {
-          // If something is going wrong we will reset the original value
-          const rename = this.$refs['groupRename' + group.id][0]
-          rename.set(event.oldValue)
-        })
-        .then(() => {
-          select.classList.remove('select-item-loading')
-        })
-    },
-    deleteGroup(id) {
-      this.$refs.groupsItemContext.hide()
-      const select = this.$refs['groupSelect' + id][0]
-      select.classList.add('select-item-loading')
-
-      this.$store.dispatch('group/delete', id).catch(() => {
-        select.classList.remove('select-item-loading')
-      })
     }
   }
 }
diff --git a/web-frontend/components/group/GroupsContextItem.vue b/web-frontend/components/group/GroupsContextItem.vue
new file mode 100644
index 000000000..a5dbde24b
--- /dev/null
+++ b/web-frontend/components/group/GroupsContextItem.vue
@@ -0,0 +1,96 @@
+<template>
+  <li
+    class="select-item"
+    :class="{
+      active: group._.selected,
+      'select-item-loading': group._.loading
+    }"
+  >
+    <div class="loading-overlay"></div>
+    <a class="select-item-link" @click="selectGroup(group)">
+      <Editable
+        ref="rename"
+        :value="group.name"
+        @change="renameGroup(group, $event)"
+      ></Editable>
+    </a>
+    <a
+      ref="contextLink"
+      class="select-item-options"
+      @click="$refs.context.toggle($refs.contextLink, 'bottom', 'right', 0)"
+    >
+      <i class="fas fa-ellipsis-v"></i>
+    </a>
+    <Context ref="context">
+      <ul class="context-menu">
+        <li>
+          <a @click="enableRename()">
+            <i class="context-menu-icon fas fa-fw fa-pen"></i>
+            Rename group
+          </a>
+        </li>
+        <li>
+          <a @click="deleteGroup(group)">
+            <i class="context-menu-icon fas fa-fw fa-trash"></i>
+            Delete group
+          </a>
+        </li>
+      </ul>
+    </Context>
+  </li>
+</template>
+
+<script>
+export default {
+  name: 'GroupsContextItem',
+  props: {
+    group: {
+      type: Object,
+      required: true
+    }
+  },
+  methods: {
+    setLoading(group, value) {
+      this.$store.dispatch('group/setItemLoading', { group, value: value })
+    },
+    enableRename() {
+      this.$refs.context.hide()
+      this.$refs.rename.edit()
+    },
+    renameGroup(group, event) {
+      this.setLoading(group, true)
+
+      this.$store
+        .dispatch('group/update', {
+          group,
+          values: {
+            name: event.value
+          }
+        })
+        .catch(() => {
+          // If something is going wrong we will reset the original value
+          this.$refs.rename.set(event.oldValue)
+        })
+        .then(() => {
+          this.setLoading(group, false)
+        })
+    },
+    selectGroup(group) {
+      this.setLoading(group, true)
+
+      this.$store.dispatch('group/select', group).then(() => {
+        this.setLoading(group, false)
+        this.$emit('selected')
+      })
+    },
+    deleteGroup(group) {
+      this.$refs.context.hide()
+      this.setLoading(group, true)
+
+      this.$store.dispatch('group/delete', group).then(() => {
+        this.setLoading(group, false)
+      })
+    }
+  }
+}
+</script>
diff --git a/web-frontend/components/sidebar/ApplicationForm.vue b/web-frontend/components/sidebar/ApplicationForm.vue
new file mode 100644
index 000000000..aa7522cbb
--- /dev/null
+++ b/web-frontend/components/sidebar/ApplicationForm.vue
@@ -0,0 +1,50 @@
+<template>
+  <form @submit.prevent="submit">
+    <div class="control">
+      <label class="control-label">
+        <i class="fas fa-font"></i>
+        Name
+      </label>
+      <div class="control-elements">
+        <input
+          ref="name"
+          v-model="values.name"
+          :class="{ 'input-error': $v.values.name.$error }"
+          type="text"
+          class="input input-large"
+          @blur="$v.values.name.$touch()"
+        />
+        <div v-if="$v.values.name.$error" class="error">
+          This field is required.
+        </div>
+      </div>
+    </div>
+    <slot></slot>
+  </form>
+</template>
+
+<script>
+import { required } from 'vuelidate/lib/validators'
+
+import form from '@/mixins/form'
+
+export default {
+  name: 'CreateApplicationForm',
+  mixins: [form],
+  data() {
+    return {
+      values: {
+        name: ''
+      }
+    }
+  },
+  mounted() {
+    this.$refs.name.focus()
+  },
+  validations: {
+    values: {
+      name: { required }
+    }
+  }
+}
+</script>
diff --git a/web-frontend/components/sidebar/CreateApplicationContext.vue b/web-frontend/components/sidebar/CreateApplicationContext.vue
new file mode 100644
index 000000000..8e587435f
--- /dev/null
+++ b/web-frontend/components/sidebar/CreateApplicationContext.vue
@@ -0,0 +1,49 @@
+<template>
+  <Context>
+    <ul class="context-menu">
+      <li v-for="(application, type) in applications" :key="type">
+        <a
+          :ref="'createApplicationModalToggle' + type"
+          @click="toggleCreateApplicationModal(type)"
+        >
+          <i
+            class="context-menu-icon fas fa-fw"
+            :class="'fa-' + application.iconClass"
+          ></i>
+          {{ application.name }}
+        </a>
+        <CreateApplicationModal
+          :ref="'createApplicationModal' + type"
+          :application="application"
+          @created="hide"
+        ></CreateApplicationModal>
+      </li>
+    </ul>
+  </Context>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+import CreateApplicationModal from '@/components/sidebar/CreateApplicationModal'
+import context from '@/mixins/context'
+
+export default {
+  name: 'CreateApplicationContext',
+  components: {
+    CreateApplicationModal
+  },
+  mixins: [context],
+  computed: {
+    ...mapState({
+      applications: state => state.application.applications
+    })
+  },
+  methods: {
+    toggleCreateApplicationModal(type) {
+      const target = this.$refs['createApplicationModalToggle' + type][0]
+      this.$refs['createApplicationModal' + type][0].toggle(target)
+    }
+  }
+}
+</script>
diff --git a/web-frontend/components/sidebar/CreateApplicationModal.vue b/web-frontend/components/sidebar/CreateApplicationModal.vue
new file mode 100644
index 000000000..655d7cd36
--- /dev/null
+++ b/web-frontend/components/sidebar/CreateApplicationModal.vue
@@ -0,0 +1,60 @@
+<template>
+  <Modal>
+    <h2 class="box-title">Create new {{ application.name | lowercase }}</h2>
+    <component
+      :is="application.getApplicationFormComponent()"
+      ref="applicationForm"
+      @submitted="submitted"
+    >
+      <div class="actions">
+        <div class="align-right">
+          <button
+            class="button button-large"
+            :class="{ 'button-loading': loading }"
+            :disabled="loading"
+          >
+            Add {{ application.name | lowercase }}
+          </button>
+        </div>
+      </div>
+    </component>
+  </Modal>
+</template>
+
+<script>
+import modal from '@/mixins/modal'
+
+export default {
+  name: 'CreateApplicationModal',
+  mixins: [modal],
+  props: {
+    application: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      loading: false
+    }
+  },
+  methods: {
+    submitted(values) {
+      this.loading = true
+      this.$store
+        .dispatch('application/create', {
+          type: this.application.type,
+          values: values
+        })
+        .then(() => {
+          this.loading = false
+          this.$emit('created')
+          this.hide()
+        })
+        .catch(() => {
+          this.loading = false
+        })
+    }
+  }
+}
+</script>
diff --git a/web-frontend/components/sidebar/Sidebar.vue b/web-frontend/components/sidebar/Sidebar.vue
new file mode 100644
index 000000000..6f9c1185d
--- /dev/null
+++ b/web-frontend/components/sidebar/Sidebar.vue
@@ -0,0 +1,54 @@
+<template>
+  <div>
+    <div v-if="hasSelectedGroup">
+      <div class="sidebar-group-title">{{ selectedGroup.name }}</div>
+      <ul class="tree">
+        <SidebarApplication
+          v-for="application in applications"
+          :key="application.id"
+          :application="application"
+        ></SidebarApplication>
+      </ul>
+      <a
+        ref="createApplicationContextLink"
+        class="sidebar-new"
+        @click="
+          $refs.createApplicationContext.toggle(
+            $refs.createApplicationContextLink
+          )
+        "
+      >
+        <i class="fas fa-plus"></i>
+        Create new
+      </a>
+      <CreateApplicationContext
+        ref="createApplicationContext"
+      ></CreateApplicationContext>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters, mapState } from 'vuex'
+
+import SidebarApplication from '@/components/sidebar/SidebarApplication'
+import CreateApplicationContext from '@/components/sidebar/CreateApplicationContext'
+
+export default {
+  name: 'Sidebar',
+  components: {
+    CreateApplicationContext,
+    SidebarApplication
+  },
+  computed: {
+    ...mapState({
+      applications: state => state.application.items,
+      selectedGroup: state => state.group.selected
+    }),
+    ...mapGetters({
+      isLoading: 'application/isLoading',
+      hasSelectedGroup: 'group/hasSelected'
+    })
+  }
+}
+</script>
diff --git a/web-frontend/components/sidebar/SidebarApplication.vue b/web-frontend/components/sidebar/SidebarApplication.vue
new file mode 100644
index 000000000..8a2a63e17
--- /dev/null
+++ b/web-frontend/components/sidebar/SidebarApplication.vue
@@ -0,0 +1,131 @@
+<template>
+  <li
+    class="tree-item"
+    :class="{
+      active: application._.selected,
+      'tree-item-loading': application._.loading
+    }"
+  >
+    <div class="tree-action">
+      <a class="tree-link" @click="selectApplication(application)">
+        <i
+          class="tree-type fas"
+          :class="'fa-' + application._.type.iconClass"
+        ></i>
+        <Editable
+          ref="rename"
+          :value="application.name"
+          @change="renameApplication(application, $event)"
+        ></Editable>
+      </a>
+      <a
+        ref="contextLink"
+        class="tree-options"
+        @click="$refs.context.toggle($refs.contextLink, 'bottom', 'right', 0)"
+      >
+        <i class="fas fa-ellipsis-v"></i>
+      </a>
+      <Context ref="context">
+        <div class="context-menu-title">{{ application.name }}</div>
+        <ul class="context-menu">
+          <li>
+            <a @click="enableRename()">
+              <i class="context-menu-icon fas fa-fw fa-pen"></i>
+              Rename {{ application._.type.name | lowercase }}
+            </a>
+          </li>
+          <li>
+            <a @click="deleteApplication(application)">
+              <i class="context-menu-icon fas fa-fw fa-trash"></i>
+              Delete {{ application._.type.name | lowercase }}
+            </a>
+          </li>
+        </ul>
+      </Context>
+    </div>
+    <template
+      v-if="
+        application._.selected && application._.type.hasSelectedSidebarComponent
+      "
+    >
+      <component
+        :is="getSelectedApplicationComponent(application)"
+        :application="application"
+      ></component>
+    </template>
+  </li>
+</template>
+
+<script>
+export default {
+  name: 'SidebarApplication',
+  props: {
+    application: {
+      type: Object,
+      required: true
+    }
+  },
+  methods: {
+    setLoading(application, value) {
+      this.$store.dispatch('application/setItemLoading', {
+        application,
+        value: value
+      })
+    },
+    enableRename() {
+      this.$refs.context.hide()
+      this.$refs.rename.edit()
+    },
+    renameApplication(application, event) {
+      this.setLoading(application, true)
+
+      this.$store
+        .dispatch('application/update', {
+          application,
+          values: {
+            name: event.value
+          }
+        })
+        .catch(() => {
+          // If something is going wrong we will reset the original value
+          this.$refs.rename.set(event.oldValue)
+        })
+        .then(() => {
+          this.setLoading(application, false)
+        })
+    },
+    selectApplication(application) {
+      this.setLoading(application, true)
+
+      this.$nuxt.$router.push(
+        {
+          name: application._.type.routeName,
+          params: {
+            id: application.id
+          }
+        },
+        () => {
+          this.setLoading(application, false)
+        },
+        () => {
+          this.setLoading(application, false)
+        }
+      )
+    },
+    deleteApplication(application) {
+      this.$refs.context.hide()
+      this.setLoading(application, true)
+
+      this.$store.dispatch('application/delete', application).then(() => {
+        this.setLoading(application, false)
+      })
+    },
+    getSelectedApplicationComponent(application) {
+      const type = this.$store.getters['application/getApplicationByType'](
+        application.type
+      )
+      return type.getSelectedSidebarComponent()
+    }
+  }
+}
+</script>
diff --git a/web-frontend/config/nuxt.config.base.js b/web-frontend/config/nuxt.config.base.js
index 2e7bc2122..b927c29d8 100644
--- a/web-frontend/config/nuxt.config.base.js
+++ b/web-frontend/config/nuxt.config.base.js
@@ -1,8 +1,8 @@
 export default {
   mode: 'universal',
 
-  /*
-   ** Headers of the page
+  /**
+   * Headers of the page
    */
   head: {
     title: 'Baserow',
@@ -12,18 +12,18 @@ export default {
     ]
   },
 
-  /*
-   ** Customize the progress-bar color
+  /**
+   * Customize the progress-bar color
    */
   loading: { color: '#fff' },
 
-  /*
-   ** Global CSS
+  /**
+   * Global CSS
    */
   css: ['@/assets/scss/default.scss'],
 
-  /*
-   ** Plugins to load before mounting the App
+  /**
+   * Plugins to load before mounting the App
    */
   plugins: [
     { src: '@/plugins/global.js' },
@@ -32,13 +32,17 @@ export default {
     { src: '@/plugins/vuelidate.js' }
   ],
 
-  /*
-   ** Nuxt.js modules
+  /**
+   * Nuxt.js modules
    */
-  modules: ['@nuxtjs/axios', 'cookie-universal-nuxt'],
+  modules: [
+    '@nuxtjs/axios',
+    'cookie-universal-nuxt',
+    '@/modules/database/module.js'
+  ],
 
   router: {
-    middleware: 'authentication'
+    middleware: ['authentication', 'group']
   },
 
   env: {
diff --git a/web-frontend/core/applications.js b/web-frontend/core/applications.js
new file mode 100644
index 000000000..8e7380eff
--- /dev/null
+++ b/web-frontend/core/applications.js
@@ -0,0 +1,92 @@
+import ApplicationForm from '@/components/sidebar/ApplicationForm'
+
+/**
+ * The application base class that can be extended when creating a plugin for
+ * the frontend.
+ */
+export class Application {
+  /**
+   * Must return a string with the unique name, this must be the same as the
+   * type used in the backend.
+   */
+  getType() {
+    return null
+  }
+
+  /**
+   * The font awesome 5 icon name that is used as convenience for the user to
+   * recognize certain application types. If you for example want the database
+   * icon, you must return 'database' here. This will result in the classname
+   * 'fas fa-database'.
+   */
+  getIconClass() {
+    return null
+  }
+
+  /**
+   * A human readable name of the application.
+   */
+  getName() {
+    return null
+  }
+
+  /**
+   * Must return the route name where the application can navigate to when the
+   * application is selected.
+   */
+  getRouteName() {
+    return null
+  }
+
+  /**
+   * The form component that will be rendered when creating a new instance of
+   * this application. By default the ApplicationForm component is returned, but
+   * this only contains a name field. If custom fields are required upon
+   * creating they can be added with this component.
+   */
+  getApplicationFormComponent() {
+    return ApplicationForm
+  }
+
+  /**
+   * The sidebar component that will be rendered when an application instance
+   * is selected. By default no component will rendered. This could be used for
+   * example to render a list of tables that belong to a database.
+   */
+  getSelectedSidebarComponent() {
+    return null
+  }
+
+  constructor() {
+    this.type = this.getType()
+    this.iconClass = this.getIconClass()
+    this.name = this.getName()
+    this.routeName = this.getRouteName()
+
+    if (this.type === null) {
+      throw new Error('The type name of an application must be set.')
+    }
+    if (this.iconClass === null) {
+      throw new Error('The icon class of an application must be set.')
+    }
+    if (this.name === null) {
+      throw new Error('The name of an application must be set.')
+    }
+    if (this.routeName === null) {
+      throw new Error('The route name of an application must be set.')
+    }
+  }
+
+  /**
+   * @return object
+   */
+  serialize() {
+    return {
+      type: this.type,
+      iconClass: this.iconClass,
+      name: this.name,
+      routeName: this.routeName,
+      hasSelectedSidebarComponent: this.getSelectedSidebarComponent() !== null
+    }
+  }
+}
diff --git a/web-frontend/filters/lowercase.js b/web-frontend/filters/lowercase.js
new file mode 100644
index 000000000..cef6ad767
--- /dev/null
+++ b/web-frontend/filters/lowercase.js
@@ -0,0 +1,9 @@
+/**
+ * Converts a string to the same string, but with lowercase characters.
+ */
+export default function(value) {
+  if (!value) {
+    return ''
+  }
+  return value.toString().toLowerCase()
+}
diff --git a/web-frontend/layouts/app.vue b/web-frontend/layouts/app.vue
index 808956f51..681c4a542 100644
--- a/web-frontend/layouts/app.vue
+++ b/web-frontend/layouts/app.vue
@@ -57,6 +57,7 @@
             <div class="sidebar-title">
               <img src="@/static/img/logo.svg" alt="" />
             </div>
+            <Sidebar></Sidebar>
           </nav>
         </div>
         <div class="sidebar-footer">
@@ -78,12 +79,14 @@ import { mapActions, mapGetters } from 'vuex'
 
 import Notifications from '@/components/notifications/Notifications'
 import GroupsContext from '@/components/group/GroupsContext'
+import Sidebar from '@/components/sidebar/Sidebar'
 
 export default {
   middleware: 'authenticated',
   components: {
     GroupsContext,
-    Notifications
+    Notifications,
+    Sidebar
   },
   computed: {
     ...mapGetters({
diff --git a/web-frontend/middleware/group.js b/web-frontend/middleware/group.js
new file mode 100644
index 000000000..9fc2543b1
--- /dev/null
+++ b/web-frontend/middleware/group.js
@@ -0,0 +1,33 @@
+import { getGroupCookie, unsetGroupCookie } from '@/utils/group'
+
+/**
+ * This middleware checks if there is a saved group id in the cookies. If set
+ * it will fetch the groups, and related application of that group.
+ */
+export default function({ store, req, app }) {
+  // If nuxt generate, pass this middleware
+  if (process.server && !req) return
+
+  // Get the selected group id
+  const groupId = getGroupCookie(app.$cookies)
+
+  // If a group id cookie is set, the user is authenticated and a selected group
+  // is not already set then we will fetch the groups and select that group.
+  if (
+    groupId &&
+    store.getters['auth/isAuthenticated'] &&
+    !store.getters['group/hasSelected']
+  ) {
+    return store
+      .dispatch('group/fetchAll')
+      .catch(() => {
+        unsetGroupCookie(app.$cookies)
+      })
+      .then(() => {
+        const group = store.getters['group/get'](groupId)
+        if (group) {
+          return store.dispatch('group/select', group)
+        }
+      })
+  }
+}
diff --git a/web-frontend/mixins/application.js b/web-frontend/mixins/application.js
new file mode 100644
index 000000000..d98aa23ba
--- /dev/null
+++ b/web-frontend/mixins/application.js
@@ -0,0 +1,30 @@
+import { notify404 } from '@/utils/error'
+
+/**
+ * This mixin can be used in combination with the component where the
+ * application routes to when selected. It will make sure that the application
+ * preSelect action is called so that the all the depending information is
+ * loaded. If something goes wrong while loading this information it will show a
+ * standard error.
+ */
+export default {
+  props: {
+    id: {
+      type: Number,
+      required: true
+    }
+  },
+  mounted() {
+    this.$store.dispatch('application/preSelect', this.id).catch(error => {
+      notify404(
+        this.$store.dispatch,
+        error,
+        'Application not found.',
+        "The application with the provided id doesn't exist or you " +
+          "don't have access to it."
+      )
+
+      this.$nuxt.$router.push({ name: 'app' })
+    })
+  }
+}
diff --git a/web-frontend/mixins/context.js b/web-frontend/mixins/context.js
index 784338d0f..cb4b55112 100644
--- a/web-frontend/mixins/context.js
+++ b/web-frontend/mixins/context.js
@@ -13,13 +13,13 @@ export default {
       }
     },
     toggle(...args) {
-      this.getRootModal().toggle(...args)
+      this.getRootContext().toggle(...args)
     },
     show(...args) {
-      this.getRootModal().show(...args)
+      this.getRootContext().show(...args)
     },
     hide(...args) {
-      this.getRootModal().hide(...args)
+      this.getRootContext().hide(...args)
     }
   }
 }
diff --git a/web-frontend/mixins/moveToBody.js b/web-frontend/mixins/moveToBody.js
index c5f715f2c..a74c3ac81 100644
--- a/web-frontend/mixins/moveToBody.js
+++ b/web-frontend/mixins/moveToBody.js
@@ -1,26 +1,69 @@
 export default {
-  /**
-   * Because we don't want the parent context to close when a user clicks 'outside' that
-   * element and in the child element we need to register the child with their parent to
-   * prevent this.
-   */
-  mounted() {
-    let $parent = this.$parent
-    while ($parent !== undefined) {
-      if ($parent.registerContextChild) {
-        $parent.registerContextChild(this)
+  data() {
+    return {
+      moveToBody: {
+        children: [],
+        hasMoved: false,
+        movedEventHandlers: []
       }
-      $parent = $parent.$parent
     }
-
-    // Move the rendered element to the top of the body so it can be positioned over any
-    // other element.
-    const body = document.body
-    body.insertBefore(this.$el, body.firstChild)
   },
   /**
-   * Make sure the context menu is not open and all the events on the body are removed
-   * and that the element is removed from the body.
+   * Because we want the to be able to stack nested elements that are moved to
+   * the body, they have to be placed at the correct position. If it has no
+   * parent is must be moved to the top of the body, but if there is a parent it
+   * must be directly under that so it will always display on over of that
+   * component.
+   */
+  mounted() {
+    let parent = this.$parent
+    let first = null
+
+    // Loop over the parent components to register himself als child in order
+    // to prevent closing when clicking in a child. We also check which parent
+    // is first so can correctly move the element.
+    while (parent !== undefined) {
+      if (parent.hasOwnProperty('moveToBody')) {
+        parent.registerMoveToBodyChild(this)
+        if (first === null) {
+          first = parent
+        }
+      }
+      parent = parent.$parent
+    }
+
+    if (first) {
+      // If there is a parent where we can register we want to position the
+      // element directly after that one so it will always be positioned over
+      // the parent when opened.
+      const handler = () => {
+        // Some times we have to wait for elements to render like with v-if.
+        this.$nextTick(() => {
+          first.$el.parentNode.insertBefore(this.$el, first.$el.nextSibling)
+          this.fireMovedToBodyHandlers()
+        })
+      }
+
+      // If the element has already moved to the body we can directly move it to
+      // the correct position. If not we have to wait until it will move.
+      if (first.moveToBody.hasMoved) {
+        handler()
+      } else {
+        first.addMovedToBodyHandler(handler)
+      }
+    } else {
+      // Because there is no parent we can directly move the component to the
+      // top of the body so it will be positioned over any other element.
+      const body = document.body
+      body.insertBefore(this.$el, body.firstChild)
+      this.fireMovedToBodyHandlers()
+    }
+
+    this.moveToBody.hasMoved = true
+  },
+  /**
+   * Make sure the context menu is not open and all the events on the body are
+   * removed and that the element is removed from the body.
    */
   destroyed() {
     this.hide()
@@ -28,5 +71,26 @@ export default {
     if (this.$el.parentNode) {
       this.$el.parentNode.removeChild(this.$el)
     }
+  },
+  methods: {
+    /**
+     * Event handlers when the element has moved to the body can be registered
+     * here.
+     */
+    addMovedToBodyHandler(handler) {
+      this.moveToBody.movedEventHandlers.push(handler)
+    },
+    /**
+     *
+     */
+    fireMovedToBodyHandlers() {
+      this.moveToBody.movedEventHandlers.forEach(handler => handler())
+    },
+    /**
+     *
+     */
+    registerMoveToBodyChild(child) {
+      this.moveToBody.children.push(child)
+    }
   }
 }
diff --git a/web-frontend/modules/database/application.js b/web-frontend/modules/database/application.js
new file mode 100644
index 000000000..16a60c2b9
--- /dev/null
+++ b/web-frontend/modules/database/application.js
@@ -0,0 +1,24 @@
+import { Application } from '@/core/applications'
+import Sidebar from '@/modules/database/components/Sidebar'
+
+export class DatabaseApplication extends Application {
+  getType() {
+    return 'database'
+  }
+
+  getIconClass() {
+    return 'database'
+  }
+
+  getName() {
+    return 'Database'
+  }
+
+  getRouteName() {
+    return 'application-database'
+  }
+
+  getSelectedSidebarComponent() {
+    return Sidebar
+  }
+}
diff --git a/web-frontend/modules/database/components/Sidebar.vue b/web-frontend/modules/database/components/Sidebar.vue
new file mode 100644
index 000000000..0145d5cf5
--- /dev/null
+++ b/web-frontend/modules/database/components/Sidebar.vue
@@ -0,0 +1,38 @@
+<template>
+  <div>
+    <ul class="tree-subs">
+      <li class="tree-sub active">
+        <a href="#" class="tree-sub-link">@TODO</a>
+        <a
+          class="tree-options"
+          @click="
+            $refs.context.toggle($event.currentTarget, 'bottom', 'right', 0)
+          "
+        >
+          <i class="fas fa-ellipsis-v"></i>
+        </a>
+        <Context ref="context">
+          <div class="context-menu-title">@TODO</div>
+          <ul class="context-menu">
+            <li>
+              <a>
+                <i class="context-menu-icon fas fa-fw fa-pen"></i>
+                Rename
+              </a>
+            </li>
+            <li>
+              <a>
+                <i class="context-menu-icon fas fa-fw fa-trash"></i>
+                Delete
+              </a>
+            </li>
+          </ul>
+        </Context>
+      </li>
+    </ul>
+    <a href="#" class="tree-sub-add">
+      <i class="fas fa-plus"></i>
+      Create table
+    </a>
+  </div>
+</template>
diff --git a/web-frontend/modules/database/module.js b/web-frontend/modules/database/module.js
new file mode 100644
index 000000000..ab867ce18
--- /dev/null
+++ b/web-frontend/modules/database/module.js
@@ -0,0 +1,15 @@
+import path from 'path'
+
+import { databaseRoutes } from './routes'
+
+export default function DatabaseModule(options) {
+  // Add the plugin to register the database application.
+  this.addPlugin({
+    src: path.resolve(__dirname, 'plugin.js'),
+    filename: 'plugin.js'
+  })
+
+  this.extendRoutes(routes => {
+    routes.push(...databaseRoutes)
+  })
+}
diff --git a/web-frontend/modules/database/pages/Database.vue b/web-frontend/modules/database/pages/Database.vue
new file mode 100644
index 000000000..a1017bf21
--- /dev/null
+++ b/web-frontend/modules/database/pages/Database.vue
@@ -0,0 +1,29 @@
+<template>
+  <div>
+    <header class="layout-col-3-1 header">
+      <ul class="header-filter">
+        <li class="header-filter-item">&nbsp;</li>
+      </ul>
+      <ul class="header-info">
+        <li>{{ selectedApplication.name }}</li>
+        <li>@TODO table name</li>
+      </ul>
+    </header>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+import application from '@/mixins/application'
+
+export default {
+  layout: 'app',
+  mixins: [application],
+  computed: {
+    ...mapState({
+      selectedApplication: state => state.application.selected
+    })
+  }
+}
+</script>
diff --git a/web-frontend/modules/database/plugin.js b/web-frontend/modules/database/plugin.js
new file mode 100644
index 000000000..784b13c82
--- /dev/null
+++ b/web-frontend/modules/database/plugin.js
@@ -0,0 +1,5 @@
+import { DatabaseApplication } from '@/modules/database/application'
+
+export default ({ store }) => {
+  store.dispatch('application/register', new DatabaseApplication())
+}
diff --git a/web-frontend/modules/database/routes.js b/web-frontend/modules/database/routes.js
new file mode 100644
index 000000000..cc110db65
--- /dev/null
+++ b/web-frontend/modules/database/routes.js
@@ -0,0 +1,14 @@
+import path from 'path'
+
+export const databaseRoutes = [
+  {
+    name: 'application-database',
+    path: '/database/:id',
+    component: path.resolve(__dirname, 'pages/Database.vue'),
+    props(route) {
+      const props = { ...route.params }
+      props.id = parseInt(props.id)
+      return props
+    }
+  }
+]
diff --git a/web-frontend/pages/app/index.vue b/web-frontend/pages/app/index.vue
index 5b4df3975..01d71e138 100644
--- a/web-frontend/pages/app/index.vue
+++ b/web-frontend/pages/app/index.vue
@@ -3,6 +3,17 @@
     <h1>Welcome {{ user }}</h1>
     <p>
       {{ groups }}
+      <br /><br />
+      {{ selectedGroup }}
+      <br /><br />
+      {{ applications }}
+      <br /><br />
+      {{ groupApplications }}
+      <br /><br />
+      <nuxt-link :to="{ name: 'application-database', params: { id: 1 } }">
+        <i class="fas fa-arrow-left"></i>
+        App
+      </nuxt-link>
     </p>
   </div>
 </template>
@@ -15,7 +26,10 @@ export default {
   computed: {
     ...mapState({
       user: state => state.auth.user,
-      groups: state => state.group.items
+      groups: state => state.group.items,
+      selectedGroup: state => state.group.selected,
+      applications: state => state.application.applications,
+      groupApplications: state => state.application.items
     })
   }
 }
diff --git a/web-frontend/plugins/global.js b/web-frontend/plugins/global.js
index b000c73f9..ca9de0e26 100644
--- a/web-frontend/plugins/global.js
+++ b/web-frontend/plugins/global.js
@@ -4,6 +4,10 @@ import Context from '@/components/Context'
 import Modal from '@/components/Modal'
 import Editable from '@/components/Editable'
 
+import lowercase from '@/filters/lowercase'
+
 Vue.component('Context', Context)
 Vue.component('Modal', Modal)
 Vue.component('Editable', Editable)
+
+Vue.filter('lowercase', lowercase)
diff --git a/web-frontend/services/application.js b/web-frontend/services/application.js
new file mode 100644
index 000000000..359d0e61e
--- /dev/null
+++ b/web-frontend/services/application.js
@@ -0,0 +1,19 @@
+import { client } from './client'
+
+export default {
+  fetchAll(groupId) {
+    return client.get(`/applications/group/${groupId}/`)
+  },
+  create(groupId, values) {
+    return client.post(`/applications/group/${groupId}/`, values)
+  },
+  get(applicationId) {
+    return client.get(`/applications/${applicationId}/`)
+  },
+  update(applicationId, values) {
+    return client.patch(`/applications/${applicationId}/`, values)
+  },
+  delete(applicationId) {
+    return client.delete(`/applications/${applicationId}/`)
+  }
+}
diff --git a/web-frontend/store/application.js b/web-frontend/store/application.js
new file mode 100644
index 000000000..0dfeb8552
--- /dev/null
+++ b/web-frontend/store/application.js
@@ -0,0 +1,275 @@
+import { Application } from '@/core/applications'
+import ApplicationService from '@/services/application'
+import { notify404, notifyError } from '@/utils/error'
+
+function populateApplication(application, getters) {
+  const type = getters.getApplicationByType(application.type)
+
+  application._ = {
+    type: type.serialize(),
+    loading: false,
+    selected: false
+  }
+  return application
+}
+
+export const state = () => ({
+  applications: {},
+  loading: false,
+  items: [],
+  selected: {}
+})
+
+export const mutations = {
+  REGISTER(state, application) {
+    state.applications[application.type] = application
+  },
+  SET_ITEMS(state, applications) {
+    state.items = applications
+  },
+  SET_LOADING(state, value) {
+    state.loading = value
+  },
+  SET_ITEM_LOADING(state, { application, value }) {
+    application._.loading = value
+  },
+  ADD_ITEM(state, item) {
+    state.items.push(item)
+  },
+  UPDATE_ITEM(state, values) {
+    const index = state.items.findIndex(item => item.id === values.id)
+    Object.assign(state.items[index], state.items[index], values)
+  },
+  DELETE_ITEM(state, id) {
+    const index = state.items.findIndex(item => item.id === id)
+    state.items.splice(index, 1)
+  },
+  SET_SELECTED(state, group) {
+    Object.values(state.items).forEach(item => {
+      item._.selected = false
+    })
+    group._.selected = true
+    state.selected = group
+  },
+  UNSELECT(state) {
+    Object.values(state.items).forEach(item => {
+      item._.selected = false
+    })
+    state.selected = {}
+  }
+}
+
+export const actions = {
+  /**
+   * Register a new application within the registry. The is commonly used when
+   * creating an extension.
+   */
+  register({ commit }, application) {
+    if (!(application instanceof Application)) {
+      throw Error('The application must be an instance of Application.')
+    }
+
+    commit('REGISTER', application)
+  },
+  /**
+   * Changes the loading state of a specific item.
+   */
+  setItemLoading({ commit }, { application, value }) {
+    commit('SET_ITEM_LOADING', { application, value })
+  },
+  /**
+   * Fetches all the applications of a given group. The is mostly called when
+   * the user selects a different group.
+   */
+  fetchAll({ commit, getters, dispatch }, group) {
+    commit('SET_LOADING', true)
+
+    return ApplicationService.fetchAll(group.id)
+      .then(({ data }) => {
+        data.forEach((part, index, d) => {
+          populateApplication(data[index], getters)
+        })
+        commit('SET_ITEMS', data)
+      })
+      .catch(error => {
+        commit('SET_ITEMS', [])
+
+        notify404(
+          dispatch,
+          error,
+          'Unable to fetch applications',
+          "You're unable to fetch the application of this group. " +
+            "This could be because you're not part of the group."
+        )
+      })
+      .then(() => {
+        commit('SET_LOADING', false)
+      })
+  },
+  /**
+   * Clears all the currently selected applications, this could be called when
+   * the group is deleted of when the user logs off.
+   */
+  clearAll({ commit }) {
+    commit('SET_ITEMS', [])
+  },
+  /**
+   * Creates a new application with the given type and values for the currently
+   * selected group.
+   */
+  create({ commit, getters, rootGetters, dispatch }, { type, values }) {
+    if (values.hasOwnProperty('type')) {
+      throw new Error(
+        'The key "type" is a reserved, but is already set on the ' +
+          'values when creating a new application.'
+      )
+    }
+
+    if (!getters.applicationTypeExists(type)) {
+      throw new Error(`An application with type "${type}" doesn't exist.`)
+    }
+
+    values.type = type
+    return ApplicationService.create(rootGetters['group/selectedId'], values)
+      .then(({ data }) => {
+        populateApplication(data, getters)
+        commit('ADD_ITEM', data)
+      })
+      .catch(error => {
+        notify404(
+          dispatch,
+          error,
+          'Could not create application',
+          "You're unable to create a new application for the selected " +
+            "group. This could be because you're not part of the group."
+        )
+      })
+  },
+  /**
+   * Updates the values of an existing application.
+   */
+  update({ commit, dispatch }, { application, values }) {
+    return ApplicationService.update(application.id, values)
+      .then(({ data }) => {
+        commit('UPDATE_ITEM', data)
+      })
+      .catch(error => {
+        notifyError(
+          dispatch,
+          error,
+          'ERROR_USER_NOT_IN_GROUP',
+          'Rename not allowed',
+          "You're not allowed to rename the application because you're " +
+            'not part of the group where the application is in.'
+        )
+      })
+  },
+  /**
+   * Deletes an existing application.
+   */
+  delete({ commit, dispatch }, application) {
+    return ApplicationService.delete(application.id)
+      .then(() => {
+        commit('DELETE_ITEM', application.id)
+      })
+      .catch(error => {
+        notifyError(
+          dispatch,
+          error,
+          'ERROR_USER_NOT_IN_GROUP',
+          'Delete not allowed',
+          "You're not allowed to rename the application because you're" +
+            ' not part of the group where the application is in.'
+        )
+      })
+  },
+  /**
+   * Select an application.
+   */
+  select({ commit }, application) {
+    commit('SET_SELECTED', application)
+  },
+  /**
+   * Select an application by a given application id.
+   */
+  selectById({ dispatch, getters }, id) {
+    const application = getters.get(id)
+    if (application === undefined) {
+      throw new Error(`Application with id ${id} is not found.`)
+    }
+    return dispatch('select', application)
+  },
+  /**
+   * Unselect the
+   */
+  unselect({ commit }) {
+    commit('UNSELECT', {})
+  },
+  /**
+   * The preSelect action will eventually select an application, but it will
+   * first check which information still needs to be loaded. For example if
+   * no group or not the group where the application is in loaded it will then
+   * first fetch that group and related application so that the sidebar is up
+   * to date. In short it will make sure that the depending state of the given
+   * application will be there.
+   */
+  preSelect({ dispatch, getters, rootGetters }, id) {
+    // First we will check if the application is already in the items.
+    const application = getters.get(id)
+
+    // If the application is already selected we don't have to do anything.
+    if (application !== undefined && application._.selected) {
+      return
+    }
+
+    // This function will select a group by its id which will then automatically
+    // fetch the applications related to that group. When done it will select
+    // the provided application id.
+    const selectGroupAndApplication = (groupId, applicationId) => {
+      return dispatch('group/selectById', groupId, {
+        root: true
+      }).then(() => {
+        return dispatch('selectById', applicationId)
+      })
+    }
+
+    if (application !== undefined) {
+      // If the application is already in the selected groups, which means that
+      // the groups and applications are already loaded, we can just select that
+      // application.
+      dispatch('select', application)
+    } else {
+      // The application is not in the selected group so we need to figure out
+      // in which he is by fetching the application.
+      return ApplicationService.get(id).then(data => {
+        if (!rootGetters['group/isLoaded']) {
+          // If the groups are not already loaded we need to load them first.
+          return dispatch('group/fetchAll', {}, { root: true }).then(() => {
+            return selectGroupAndApplication(data.data.group.id, id)
+          })
+        } else {
+          // The groups are already loaded so we
+          return selectGroupAndApplication(data.data.group.id, id)
+        }
+      })
+    }
+  }
+}
+
+export const getters = {
+  isLoading(state) {
+    return state.loading
+  },
+  get: state => id => {
+    return state.items.find(item => item.id === id)
+  },
+  applicationTypeExists: state => type => {
+    return state.applications.hasOwnProperty(type)
+  },
+  getApplicationByType: state => type => {
+    if (!state.applications.hasOwnProperty(type)) {
+      throw new Error(`An application with type "${type}" doesn't exist.`)
+    }
+    return state.applications[type]
+  }
+}
diff --git a/web-frontend/store/auth.js b/web-frontend/store/auth.js
index ceb43ab44..00bff617c 100644
--- a/web-frontend/store/auth.js
+++ b/web-frontend/store/auth.js
@@ -2,6 +2,7 @@ import jwtDecode from 'jwt-decode'
 
 import AuthService from '@/services/auth'
 import { setToken, unsetToken } from '@/utils/auth'
+import { unsetGroupCookie } from '@/utils/group'
 
 export const state = () => ({
   refreshing: false,
@@ -53,9 +54,12 @@ export const actions = {
    * Logs off the user by removing the token as a cookie and clearing the user
    * data.
    */
-  logoff({ commit }) {
+  logoff({ commit, dispatch }) {
     unsetToken(this.app.$cookies)
+    unsetGroupCookie(this.app.$cookies)
     commit('CLEAR_USER_DATA')
+    dispatch('group/clearAll', {}, { root: true })
+    dispatch('group/unselect', {}, { root: true })
   },
   /**
    * Refresh the existing token. If successful commit the new token and start a
diff --git a/web-frontend/store/group.js b/web-frontend/store/group.js
index 4d4ab10a1..d19eab34c 100644
--- a/web-frontend/store/group.js
+++ b/web-frontend/store/group.js
@@ -1,11 +1,17 @@
-// import { set } from 'vue'
-
 import GroupService from '@/services/group'
+import { notify404 } from '@/utils/error'
+import { setGroupCookie, unsetGroupCookie } from '@/utils/group'
+
+function populateGroup(group) {
+  group._ = { loading: false, selected: false }
+  return group
+}
 
 export const state = () => ({
   loaded: false,
   loading: false,
-  items: []
+  items: [],
+  selected: {}
 })
 
 export const mutations = {
@@ -16,9 +22,20 @@ export const mutations = {
     state.loading = loading
   },
   SET_ITEMS(state, items) {
-    state.items = items
+    // Set some default values that we might need later.
+    state.items = items.map(item => {
+      item = populateGroup(item)
+      return item
+    })
+  },
+  SET_ITEM_LOADING(state, { group, value }) {
+    if (!group.hasOwnProperty('_')) {
+      return
+    }
+    group._.loading = value
   },
   ADD_ITEM(state, item) {
+    item = populateGroup(item)
     state.items.push(item)
   },
   UPDATE_ITEM(state, values) {
@@ -28,15 +45,48 @@ export const mutations = {
   DELETE_ITEM(state, id) {
     const index = state.items.findIndex(item => item.id === id)
     state.items.splice(index, 1)
+  },
+  SET_SELECTED(state, group) {
+    Object.values(state.items).forEach(item => {
+      item._.selected = false
+    })
+    group._.selected = true
+    state.selected = group
+  },
+  UNSELECT(state) {
+    Object.values(state.items).forEach(item => {
+      item._.selected = false
+    })
+    state.selected = {}
   }
 }
 
 export const actions = {
+  /**
+   * If not already loading or loaded it will trigger the fetchAll action which
+   * will load all the groups for the user.
+   */
   loadAll({ state, dispatch }) {
     if (!state.loaded && !state.loading) {
       dispatch('fetchAll')
     }
   },
+  /**
+   * Clears all the selected groups. Can be used when logging off.
+   */
+  clearAll({ commit }) {
+    commit('SET_ITEMS', [])
+    commit('SET_LOADED', false)
+  },
+  /**
+   * Changes the loading state of a specific group.
+   */
+  setItemLoading({ commit }, { group, value }) {
+    commit('SET_ITEM_LOADING', { group, value })
+  },
+  /**
+   * Fetches all the groups of an authenticated user.
+   */
   fetchAll({ commit }) {
     commit('SET_LOADING', true)
 
@@ -52,21 +102,79 @@ export const actions = {
         commit('SET_LOADING', false)
       })
   },
+  /**
+   * Creates a new group with the given values.
+   */
   create({ commit }, values) {
     return GroupService.create(values).then(({ data }) => {
       commit('ADD_ITEM', data)
     })
   },
-  update({ commit }, { id, values }) {
-    return GroupService.update(id, values).then(({ data }) => {
-      commit('UPDATE_ITEM', data)
-    })
+  /**
+   * Updates the values of the group with the provided id.
+   */
+  update({ commit, dispatch }, { group, values }) {
+    return GroupService.update(group.id, values)
+      .then(({ data }) => {
+        commit('UPDATE_ITEM', data)
+      })
+      .catch(error => {
+        notify404(
+          dispatch,
+          error,
+          'Unable to rename',
+          "You're unable to rename the group. This could be because " +
+            "you're not part of the group."
+        )
+      })
   },
-  delete({ commit }, id) {
-    return GroupService.delete(id).then(() => {
-      console.log(id)
-      commit('DELETE_ITEM', id)
-    })
+  /**
+   * Deletes an existing group with the provided id.
+   */
+  delete({ commit, dispatch }, group) {
+    return GroupService.delete(group.id)
+      .then(() => {
+        if (group._.selected) {
+          dispatch('unselect', group)
+        }
+
+        commit('DELETE_ITEM', group.id)
+      })
+      .catch(error => {
+        notify404(
+          dispatch,
+          error,
+          'Unable to delete',
+          "You're unable to delete the group. This could be because " +
+            "you're not part of the group."
+        )
+      })
+  },
+  /**
+   * Select a group and fetch all the applications related to that group.
+   */
+  select({ commit, dispatch }, group) {
+    commit('SET_SELECTED', group)
+    setGroupCookie(group.id, this.app.$cookies)
+    return dispatch('application/fetchAll', group, { root: true })
+  },
+  /**
+   * Select a group by a given group id.
+   */
+  selectById({ dispatch, getters }, id) {
+    const group = getters.get(id)
+    if (group === undefined) {
+      throw new Error(`Group with id ${id} is not found.`)
+    }
+    return dispatch('select', group)
+  },
+  /**
+   * Unselect a group if selected and clears all the fetched applications.
+   */
+  unselect({ commit, dispatch, getters }, group) {
+    commit('UNSELECT', {})
+    unsetGroupCookie(this.app.$cookies)
+    return dispatch('application/clearAll', group, { root: true })
   }
 }
 
@@ -76,5 +184,18 @@ export const getters = {
   },
   isLoading(state) {
     return state.loading
+  },
+  get: state => id => {
+    return state.items.find(item => item.id === id)
+  },
+  hasSelected(state) {
+    return state.selected.hasOwnProperty('id')
+  },
+  selectedId(state) {
+    if (!state.selected.hasOwnProperty('id')) {
+      throw new Error('There is no selected group.')
+    }
+
+    return state.selected.id
   }
 }
diff --git a/web-frontend/store/notification.js b/web-frontend/store/notification.js
index b1746c03f..10c240a5a 100644
--- a/web-frontend/store/notification.js
+++ b/web-frontend/store/notification.js
@@ -15,6 +15,9 @@ export const mutations = {
 }
 
 export const actions = {
+  /**
+   * Shows a notification message to the user.
+   */
   add({ commit }, { type, title, message }) {
     commit('ADD', {
       id: uuid(),
diff --git a/web-frontend/utils/error.js b/web-frontend/utils/error.js
new file mode 100644
index 000000000..5eebe08c6
--- /dev/null
+++ b/web-frontend/utils/error.js
@@ -0,0 +1,32 @@
+/**
+ * Adds a notification error if the error response has 404 status code.
+ */
+export function notify404(dispatch, error, title, message) {
+  if (error.response && error.response.status === 404) {
+    dispatch(
+      'notification/error',
+      {
+        title: title,
+        message: message
+      },
+      { root: true }
+    )
+  }
+}
+
+/**
+ * Adds a notification error if the response error is equal to the provided
+ * error code.
+ */
+export function notifyError(dispatch, error, errorCode, title, message) {
+  if (error.responseError === errorCode) {
+    dispatch(
+      'notification/error',
+      {
+        title: title,
+        message: message
+      },
+      { root: true }
+    )
+  }
+}
diff --git a/web-frontend/utils/group.js b/web-frontend/utils/group.js
new file mode 100644
index 000000000..0666bf107
--- /dev/null
+++ b/web-frontend/utils/group.js
@@ -0,0 +1,16 @@
+const cookieGroupName = 'baserow_group_id'
+
+export const setGroupCookie = (groupId, cookie) => {
+  if (process.SERVER_BUILD) return
+  cookie.set(cookieGroupName, groupId)
+}
+
+export const unsetGroupCookie = cookie => {
+  if (process.SERVER_BUILD) return
+  cookie.remove(cookieGroupName)
+}
+
+export const getGroupCookie = cookie => {
+  if (process.SERVER_BUILD) return
+  return cookie.get(cookieGroupName)
+}