diff --git a/app/assets/javascripts/custom_emoji/components/app.vue b/app/assets/javascripts/custom_emoji/components/app.vue
new file mode 100644
index 0000000000000000000000000000000000000000..405a296397fe15a8cbc8073956876415801854d9
--- /dev/null
+++ b/app/assets/javascripts/custom_emoji/components/app.vue
@@ -0,0 +1,15 @@
+<script>
+export default {};
+</script>
+
+<template>
+  <div class="row gl-mt-5">
+    <div class="col-12">
+      <h4 class="gl-mt-0">
+        {{ __('Custom emoji') }}
+      </h4>
+      <p>{{ __('Custom emoji will be available to use in every project in group.') }}</p>
+      <router-view />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/custom_emoji/custom_emoji_bundle.js b/app/assets/javascripts/custom_emoji/custom_emoji_bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..1d8e64e605b974603fe8bf19ff1cd80400d0295b
--- /dev/null
+++ b/app/assets/javascripts/custom_emoji/custom_emoji_bundle.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import routes from './routes';
+import App from './components/app.vue';
+
+export const initCustomEmojis = () => {
+  Vue.use(VueApollo);
+  Vue.use(VueRouter);
+
+  const el = document.getElementById('js-custom-emojis-root');
+
+  if (!el) return;
+
+  const apolloProvider = new VueApollo({
+    defaultClient: createDefaultClient(),
+  });
+  const router = new VueRouter({
+    base: el.dataset.basePath,
+    mode: 'history',
+    routes,
+  });
+  const { groupPath } = el.dataset;
+
+  // eslint-disable-next-line no-new
+  new Vue({
+    el,
+    name: 'CustomEmojiApp',
+    router,
+    apolloProvider,
+    provide: {
+      groupPath,
+    },
+    render(createElement) {
+      return createElement(App);
+    },
+  });
+};
diff --git a/app/assets/javascripts/custom_emoji/pages/index.vue b/app/assets/javascripts/custom_emoji/pages/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6d32ba41eaeeabe09ad6fdad0a62eab41c894cde
--- /dev/null
+++ b/app/assets/javascripts/custom_emoji/pages/index.vue
@@ -0,0 +1,7 @@
+<script>
+export default {};
+</script>
+
+<template>
+  <div></div>
+</template>
diff --git a/app/assets/javascripts/custom_emoji/pages/new.vue b/app/assets/javascripts/custom_emoji/pages/new.vue
new file mode 100644
index 0000000000000000000000000000000000000000..659c1a0bfd38414eb7e45d13270e67c2e3651803
--- /dev/null
+++ b/app/assets/javascripts/custom_emoji/pages/new.vue
@@ -0,0 +1,11 @@
+<script>
+export default {};
+</script>
+
+<template>
+  <div>
+    <h5 class="gl-mt-0 gl-font-lg">
+      {{ __('Add new emoji') }}
+    </h5>
+  </div>
+</template>
diff --git a/app/assets/javascripts/custom_emoji/routes.js b/app/assets/javascripts/custom_emoji/routes.js
new file mode 100644
index 0000000000000000000000000000000000000000..2bfbf538571ed44109def122ec2ebab7b3e2e14b
--- /dev/null
+++ b/app/assets/javascripts/custom_emoji/routes.js
@@ -0,0 +1,13 @@
+import IndexComponent from './pages/index.vue';
+import NewComponent from './pages/new.vue';
+
+export default [
+  {
+    path: '/',
+    component: IndexComponent,
+  },
+  {
+    path: '/new',
+    component: NewComponent,
+  },
+];
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9ebee38afd389f1178f559d404e7fa429fac8a68..f06b3b7035e1f19ca8c482d020228fefeff3840e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2867,6 +2867,9 @@ msgstr ""
 msgid "Add new directory"
 msgstr ""
 
+msgid "Add new emoji"
+msgstr ""
+
 msgid "Add new webhook"
 msgstr ""
 
@@ -13735,6 +13738,12 @@ msgstr ""
 msgid "Custom analyzers: language support"
 msgstr ""
 
+msgid "Custom emoji"
+msgstr ""
+
+msgid "Custom emoji will be available to use in every project in group."
+msgstr ""
+
 msgid "Custom hostname (for private commit emails)"
 msgstr ""