diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 3fcbc246..3799359f 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -148,6 +148,37 @@ const getInstancePanel = async ({ store }) => {
   }
 }
 
+const getStickers = async ({ store }) => {
+  try {
+    const res = await window.fetch('/static/stickers.json')
+    if (res.ok) {
+      const values = await res.json()
+      const stickers = (await Promise.all(
+        Object.entries(values).map(async ([name, path]) => {
+          const resPack = await window.fetch(path + 'pack.json')
+          var meta = {}
+          if (resPack.ok) {
+            meta = await resPack.json()
+          }
+          return {
+            pack: name,
+            path,
+            meta
+          }
+        })
+      )).sort((a, b) => {
+        return a.meta.title.localeCompare(b.meta.title)
+      })
+      store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
+    } else {
+      throw (res)
+    }
+  } catch (e) {
+    console.warn("Can't load stickers")
+    console.warn(e)
+  }
+}
+
 const getStaticEmoji = async ({ store }) => {
   try {
     const res = await window.fetch('/static/emoji.json')
@@ -286,6 +317,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
     setConfig({ store }),
     getTOS({ store }),
     getInstancePanel({ store }),
+    getStickers({ store }),
     getStaticEmoji({ store }),
     getCustomEmoji({ store }),
     getNodeInfo({ store })
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 36d0d74f..40bbf6d4 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -3,6 +3,7 @@ import MediaUpload from '../media_upload/media_upload.vue'
 import ScopeSelector from '../scope_selector/scope_selector.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
 import PollForm from '../poll/poll_form.vue'
+import StickerPicker from '../sticker_picker/sticker_picker.vue'
 import fileTypeService from '../../services/file_type/file_type.service.js'
 import { reject, map, uniqBy } from 'lodash'
 import suggestor from '../emoji-input/suggestor.js'
@@ -34,6 +35,7 @@ const PostStatusForm = {
     MediaUpload,
     EmojiInput,
     PollForm,
+    StickerPicker,
     ScopeSelector
   },
   mounted () {
@@ -82,7 +84,8 @@ const PostStatusForm = {
         contentType
       },
       caret: 0,
-      pollFormVisible: false
+      pollFormVisible: false,
+      stickerPickerVisible: false
     }
   },
   computed: {
@@ -158,6 +161,12 @@ const PostStatusForm = {
     safeDMEnabled () {
       return this.$store.state.instance.safeDM
     },
+    stickersAvailable () {
+      if (this.$store.state.instance.stickers) {
+        return this.$store.state.instance.stickers.length > 0
+      }
+      return 0
+    },
     pollsAvailable () {
       return this.$store.state.instance.pollsAvailable &&
         this.$store.state.instance.pollLimits.max_options >= 2
@@ -213,6 +222,7 @@ const PostStatusForm = {
             poll: {}
           }
           this.pollFormVisible = false
+          this.stickerPickerVisible = false
           this.$refs.mediaUpload.clearFile()
           this.clearPollForm()
           this.$emit('posted')
@@ -229,6 +239,7 @@ const PostStatusForm = {
     addMediaFile (fileInfo) {
       this.newStatus.files.push(fileInfo)
       this.enableSubmit()
+      this.stickerPickerVisible = false
     },
     removeMediaFile (fileInfo) {
       let index = this.newStatus.files.indexOf(fileInfo)
@@ -288,6 +299,14 @@ const PostStatusForm = {
     changeVis (visibility) {
       this.newStatus.visibility = visibility
     },
+    toggleStickerPicker () {
+      this.stickerPickerVisible = !this.stickerPickerVisible
+    },
+    clearStickerPicker () {
+      if (this.$refs.stickerPicker) {
+        this.$refs.stickerPicker.clear()
+      }
+    },
     togglePollForm () {
       this.pollFormVisible = !this.pollFormVisible
     },
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 1f389eda..d29d47e4 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -157,6 +157,17 @@
             @uploaded="addMediaFile"
             @upload-failed="uploadFailed"
           />
+          <div
+            v-if="stickersAvailable"
+            class="sticker-icon"
+          >
+            <i
+              :title="$t('stickers.add_sticker')"
+              class="icon-picture btn btn-default"
+              :class="{ selected: stickerPickerVisible }"
+              @click="toggleStickerPicker"
+            />
+          </div>
           <div
             v-if="pollsAvailable"
             class="poll-icon"
@@ -169,7 +180,6 @@
             />
           </div>
         </div>
-
         <button
           v-if="posting"
           disabled
@@ -248,6 +258,11 @@
         <label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label>
       </div>
     </form>
+    <sticker-picker
+      v-if="stickerPickerVisible"
+      ref="stickerPicker"
+      @uploaded="addMediaFile"
+    />
   </div>
 </template>
 
@@ -310,7 +325,7 @@
     }
   }
 
-  .poll-icon {
+  .poll-icon, .sticker-icon {
     font-size: 26px;
     flex: 1;
 
@@ -320,6 +335,11 @@
     }
   }
 
+  .sticker-icon {
+    flex: 0;
+    min-width: 50px;
+  }
+
   .icon-chart-bar {
     cursor: pointer;
   }
diff --git a/src/components/sticker_picker/sticker_picker.js b/src/components/sticker_picker/sticker_picker.js
new file mode 100644
index 00000000..a6dcded3
--- /dev/null
+++ b/src/components/sticker_picker/sticker_picker.js
@@ -0,0 +1,52 @@
+/* eslint-env browser */
+import statusPosterService from '../../services/status_poster/status_poster.service.js'
+import TabSwitcher from '../tab_switcher/tab_switcher.js'
+
+const StickerPicker = {
+  components: [
+    TabSwitcher
+  ],
+  data () {
+    return {
+      meta: {
+        stickers: []
+      },
+      path: ''
+    }
+  },
+  computed: {
+    pack () {
+      return this.$store.state.instance.stickers || []
+    }
+  },
+  methods: {
+    clear () {
+      this.meta = {
+        stickers: []
+      }
+    },
+    pick (sticker, name) {
+      const store = this.$store
+      // TODO remove this workaround by finding a way to bypass reuploads
+      fetch(sticker)
+        .then((res) => {
+          res.blob().then((blob) => {
+            var file = new File([blob], name, { mimetype: 'image/png' })
+            var formData = new FormData()
+            formData.append('file', file)
+            statusPosterService.uploadMedia({ store, formData })
+              .then((fileData) => {
+                this.$emit('uploaded', fileData)
+                this.clear()
+              }, (error) => {
+                console.warn("Can't attach sticker")
+                console.warn(error)
+                this.$emit('upload-failed', 'default')
+              })
+          })
+        })
+    }
+  }
+}
+
+export default StickerPicker
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
new file mode 100644
index 00000000..938204c8
--- /dev/null
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -0,0 +1,62 @@
+<template>
+  <div
+    class="sticker-picker"
+  >
+    <div
+      class="sticker-picker-panel"
+    >
+      <tab-switcher
+        :render-only-focused="true"
+      >
+        <div
+          v-for="stickerpack in pack"
+          :key="stickerpack.path"
+          :image-tooltip="stickerpack.meta.title"
+          :image="stickerpack.path + stickerpack.meta.tabIcon"
+          class="sticker-picker-content"
+        >
+          <div
+            v-for="sticker in stickerpack.meta.stickers"
+            :key="sticker"
+            class="sticker"
+            @click="pick(stickerpack.path + sticker, stickerpack.meta.title)"
+          >
+            <img
+              :src="stickerpack.path + sticker"
+            >
+          </div>
+        </div>
+      </tab-switcher>
+    </div>
+  </div>
+</template>
+
+<script src="./sticker_picker.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.sticker-picker {
+  .sticker-picker-panel {
+    display: inline-block;
+    width: 100%;
+    .sticker-picker-content {
+      max-height: 300px;
+      overflow-y: scroll;
+      overflow-x: auto;
+      .sticker {
+        display: inline-block;
+        width: 20%;
+        height: 20%;
+        img {
+          width: 100%;
+          &:hover {
+            filter: drop-shadow(0 0 5px var(--link, $fallback--link));
+          }
+        }
+      }
+    }
+  }
+}
+
+</style>
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 67835231..a5fe019c 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -45,7 +45,19 @@ export default Vue.component('tab-switcher', {
           classesTab.push('active')
           classesWrapper.push('active')
         }
-
+        if (slot.data.attrs.image) {
+          return (
+            <div class={ classesWrapper.join(' ')}>
+              <button
+                disabled={slot.data.attrs.disabled}
+                onClick={this.activateTab(index)}
+                class={classesTab.join(' ')}>
+                <img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/>
+                {slot.data.attrs.label ? '' : slot.data.attrs.label}
+              </button>
+            </div>
+          )
+        }
         return (
           <div class={ classesWrapper.join(' ')}>
             <button
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index f7449439..4eeb42e0 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -53,6 +53,12 @@
           background: transparent;
           z-index: 5;
         }
+
+        img {
+          max-height: 26px;
+          vertical-align: top;
+          margin-top: -5px;
+        }
       }
 
       &:not(.active) {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index b5c6b1d8..60a3e284 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -106,6 +106,9 @@
     "expired": "Poll ended {0} ago",
     "not_enough_options": "Too few unique options in poll"
   },
+  "stickers": {
+    "add_sticker": "Add Sticker"
+  },
   "interactions": {
     "favs_repeats": "Repeats and Favorites",
     "follows": "New follows",