diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 5b2e89e51be798cf8ba3b561787ae06d9e69796d..165b3a9946838a0186c226ce3d292d4f8bf522af 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -87,6 +87,7 @@ $(() => {
       Store.rootPath = this.endpoint;
 
       this.filterManager = new FilteredSearchBoards(Store.filter, true, [(this.milestoneTitle ? 'milestone' : null)]);
+      this.filterManager.setup();
 
       // Listen for updateTokens event
       eventHub.$on('updateTokens', this.updateTokens);
diff --git a/app/assets/javascripts/boards/components/modal/filters.js b/app/assets/javascripts/boards/components/modal/filters.js
index b214b5a71994d86528957db4afb9529ec7a065c4..56a0fde5a9143c17b858fd47d0d32bf633ef9011 100644
--- a/app/assets/javascripts/boards/components/modal/filters.js
+++ b/app/assets/javascripts/boards/components/modal/filters.js
@@ -13,6 +13,7 @@ export default {
     FilteredSearchContainer.container = this.$el;
 
     this.filteredSearch = new FilteredSearchBoards(this.store);
+    this.filteredSearch.setup();
     this.filteredSearch.removeTokens();
     this.filteredSearch.handleInputPlaceholder();
     this.filteredSearch.toggleClearSearchButton();
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 5d837134c94921d5d74f57cc280103c00d4ba752..3f083655f950819300ac2b93633d8587ad644f17 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -42,9 +42,7 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
     this.filteredSearchInput.dispatchEvent(new Event('input'));
   }
 
-  canEdit(token) {
-    const tokenName = token.querySelector('.name').textContent.trim();
-
+  canEdit(tokenName) {
     return this.cantEdit.indexOf(tokenName) === -1;
   }
 }
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 9139de9c880eb9b5fd46a0126aba7a7e35771e2c..1de8ebdfb39097a12e21cfd8ba78b2bbe991583c 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -130,7 +130,10 @@ import ApproversSelect from './approvers_select';
         case 'projects:merge_requests:index':
         case 'projects:issues:index':
           if (gl.FilteredSearchManager) {
-            new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
+            const filteredSearchManager = new gl.FilteredSearchManager(
+              page === 'projects:issues:index' ? 'issues' : 'merge_requests',
+            );
+            filteredSearchManager.setup();
           }
           Issuable.init();
           new gl.IssuableBulkActions({
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 224b6b928cf09ec8bdac4f90cf879126cbce558e..1ed0b2e9896728aec56fa5d2391d0dd476264463 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -6,6 +6,7 @@ import eventHub from './event_hub';
 
 class FilteredSearchManager {
   constructor(page) {
+    this.page = page;
     this.container = FilteredSearchContainer.container;
     this.filteredSearchInput = this.container.querySelector('.filtered-search');
     this.filteredSearchInputForm = this.filteredSearchInput.form;
@@ -13,7 +14,7 @@ class FilteredSearchManager {
     this.tokensContainer = this.container.querySelector('.tokens-container');
     this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
 
-    if (page === 'issues' || page === 'boards') {
+    if (this.page === 'issues' || this.page === 'boards') {
       this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeysIssuesEE;
     }
 
@@ -21,16 +22,18 @@ class FilteredSearchManager {
       isLocalStorageAvailable: RecentSearchesService.isAvailable(),
       allowedKeys: this.filteredSearchTokenKeys.getKeys(),
     });
-    const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
-    const projectPath = searchHistoryDropdownElement ?
-      searchHistoryDropdownElement.dataset.projectFullPath : 'project';
+    this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
+    const projectPath = this.searchHistoryDropdownElement ?
+      this.searchHistoryDropdownElement.dataset.projectFullPath : 'project';
     let recentSearchesPagePrefix = 'issue-recent-searches';
-    if (page === 'merge_requests') {
+    if (this.page === 'merge_requests') {
       recentSearchesPagePrefix = 'merge-request-recent-searches';
     }
     const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`;
     this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
+  }
 
+  setup() {
     // Fetch recent searches from localStorage
     this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
       .catch((error) => {
@@ -51,12 +54,12 @@ class FilteredSearchManager {
 
     if (this.filteredSearchInput) {
       this.tokenizer = gl.FilteredSearchTokenizer;
-      this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, page);
+      this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, this.page);
 
       this.recentSearchesRoot = new RecentSearchesRoot(
         this.recentSearchesStore,
         this.recentSearchesService,
-        searchHistoryDropdownElement,
+        this.searchHistoryDropdownElement,
       );
       this.recentSearchesRoot.init();
 
@@ -145,9 +148,9 @@ class FilteredSearchManager {
     if (e.keyCode === 8 || e.keyCode === 46) {
       const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
 
-      if (this.filteredSearchInput.value === '' && lastVisualToken) {
-        if (this.canEdit && !this.canEdit(lastVisualToken)) return;
-
+      const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim();
+      const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName);
+      if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
         this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
         gl.FilteredSearchVisualTokens.removeLastTokenPartial();
       }
@@ -246,10 +249,10 @@ class FilteredSearchManager {
 
   editToken(e) {
     const token = e.target.closest('.js-visual-token');
+    const sanitizedTokenName = token.querySelector('.name').textContent.trim();
+    const canEdit = this.canEdit && this.canEdit(sanitizedTokenName);
 
-    if (this.canEdit && !this.canEdit(token)) return;
-
-    if (token) {
+    if (token && canEdit) {
       gl.FilteredSearchVisualTokens.editToken(token);
       this.tokenChange();
     }
@@ -399,7 +402,12 @@ class FilteredSearchManager {
 
       if (condition) {
         hasFilteredSearch = true;
-        gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
+        const canEdit = this.canEdit && this.canEdit(condition.tokenKey);
+        gl.FilteredSearchVisualTokens.addFilterVisualToken(
+          condition.tokenKey,
+          condition.value,
+          canEdit,
+        );
       } else {
         // Sanitize value since URL converts spaces into +
         // Replace before decode so that we know what was originally + versus the encoded +
@@ -418,18 +426,27 @@ class FilteredSearchManager {
           }
 
           hasFilteredSearch = true;
-          gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
+          const canEdit = this.canEdit && this.canEdit(sanitizedKey);
+          gl.FilteredSearchVisualTokens.addFilterVisualToken(
+            sanitizedKey,
+            `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
+            canEdit,
+          );
         } else if (!match && keyParam === 'assignee_id') {
           const id = parseInt(value, 10);
           if (usernameParams[id]) {
             hasFilteredSearch = true;
-            gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
+            const tokenName = 'assignee';
+            const canEdit = this.canEdit && this.canEdit(tokenName);
+            gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
           }
         } else if (!match && keyParam === 'author_id') {
           const id = parseInt(value, 10);
           if (usernameParams[id]) {
             hasFilteredSearch = true;
-            gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
+            const tokenName = 'author';
+            const canEdit = this.canEdit && this.canEdit(tokenName);
+            gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
           }
         } else if (!match && keyParam === 'search') {
           hasFilteredSearch = true;
@@ -524,6 +541,11 @@ class FilteredSearchManager {
     this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
     this.search();
   }
+
+  // eslint-disable-next-line class-methods-use-this
+  canEdit() {
+    return true;
+  }
 }
 
 window.gl = window.gl || {};
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index f3003b86493ea324bd3c218429a7aca1ed6d0a74..bc1226f5879112350387ecf4f0ce6766af836d92 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -36,15 +36,22 @@ class FilteredSearchVisualTokens {
     }
   }
 
-  static createVisualTokenElementHTML() {
+  static createVisualTokenElementHTML(canEdit = true) {
+    let removeTokenMarkup = '';
+    if (canEdit) {
+      removeTokenMarkup = `
+        <div class="remove-token" role="button">
+          <i class="fa fa-close"></i>
+        </div>
+      `;
+    }
+
     return `
       <div class="selectable" role="button">
         <div class="name"></div>
         <div class="value-container">
           <div class="value"></div>
-          <div class="remove-token" role="button">
-            <i class="fa fa-close"></i>
-          </div>
+          ${removeTokenMarkup}
         </div>
       </div>
     `;
@@ -84,13 +91,13 @@ class FilteredSearchVisualTokens {
     }
   }
 
-  static addVisualTokenElement(name, value, isSearchTerm) {
+  static addVisualTokenElement(name, value, isSearchTerm, canEdit) {
     const li = document.createElement('li');
     li.classList.add('js-visual-token');
     li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
 
     if (value) {
-      li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML();
+      li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML(canEdit);
       FilteredSearchVisualTokens.renderVisualTokenValue(li, name, value);
     } else {
       li.innerHTML = '<div class="name"></div>';
@@ -114,20 +121,20 @@ class FilteredSearchVisualTokens {
     }
   }
 
-  static addFilterVisualToken(tokenName, tokenValue) {
+  static addFilterVisualToken(tokenName, tokenValue, canEdit) {
     const { lastVisualToken, isLastVisualTokenValid }
       = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
     const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement;
 
     if (isLastVisualTokenValid) {
-      addVisualTokenElement(tokenName, tokenValue, false);
+      addVisualTokenElement(tokenName, tokenValue, false, canEdit);
     } else {
       const previousTokenName = lastVisualToken.querySelector('.name').innerText;
       const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
       tokensContainer.removeChild(lastVisualToken);
 
       const value = tokenValue || tokenName;
-      addVisualTokenElement(previousTokenName, value, false);
+      addVisualTokenElement(previousTokenName, value, false, canEdit);
     }
   }
 
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 4d5112d523034b1b6280d9eca18116af7c9cd96f..5d827ebb834371a43a71c41db59703a21dfd6767 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -104,6 +104,22 @@
     padding: 2px 7px;
   }
 
+  .name {
+    background-color: $filter-name-resting-color;
+    color: $filter-name-text-color;
+    border-radius: 2px 0 0 2px;
+    margin-right: 1px;
+    text-transform: capitalize;
+  }
+
+  .value-container {
+    background-color: $white-normal;
+    color: $filter-value-text-color;
+    border-radius: 0 2px 2px 0;
+    margin-right: 5px;
+    padding-right: 8px;
+  }
+
   .value {
     padding-right: 0;
   }
@@ -111,7 +127,7 @@
   .remove-token {
     display: inline-block;
     padding-left: 4px;
-    padding-right: 8px;
+    padding-right: 0;
 
     .fa-close {
       color: $gl-text-color-secondary;
@@ -132,21 +148,6 @@
     }
   }
 
-  .name {
-    background-color: $filter-name-resting-color;
-    color: $filter-name-text-color;
-    border-radius: 2px 0 0 2px;
-    margin-right: 1px;
-    text-transform: capitalize;
-  }
-
-  .value-container {
-    background-color: $white-normal;
-    color: $filter-value-text-color;
-    border-radius: 0 2px 2px 0;
-    margin-right: 5px;
-  }
-
   .selected {
     .name {
       background-color: $filter-name-selected-color;
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index f0084bc55574d672a384a9ebb7882617f99ec2dc..a8d44457351b36ce93644e89bce30acff7ea5b22 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -177,7 +177,8 @@
 
     $(document).off('page:restore').on('page:restore', function (event) {
       if (gl.FilteredSearchManager) {
-        new gl.FilteredSearchManager();
+        const filteredSearchManager = new gl.FilteredSearchManager();
+        filteredSearchManager.setup();
       }
       Issuable.init();
       new gl.IssuableBulkActions({
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3398882218f36b39b444bcb458af931f4cf61541..025f31190ab32bd5d0156c9fb6ca2229db87d009 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -4,7 +4,9 @@
   include DragTo
 
   let(:project) { create(:empty_project, :public) }
+  let(:milestone) { create(:milestone, title: "v2.2", project: project) }
   let!(:board)  { create(:board, project: project) }
+  let!(:board_with_milestone)  { create(:board, project: project, milestone: milestone) }
   let(:user)    { create(:user) }
   let!(:user2)  { create(:user) }
 
@@ -509,6 +511,22 @@
     end
   end
 
+  context 'locked milestone' do
+    before do
+      visit namespace_project_board_path(project.namespace, project, board_with_milestone)
+      wait_for_requests
+    end
+
+    it 'should not have remove button' do
+      expect(page).to have_selector('.js-visual-token .remove-token', count: 0)
+    end
+
+    it 'should not be able to be backspaced' do
+      find('.input-token .filtered-search').native.send_key(:backspace)
+      expect(page).to have_selector('.js-visual-token', count: 1)
+    end
+  end
+
   context 'keyboard shortcuts' do
     before do
       visit namespace_project_boards_path(project.namespace, project)
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 8688332782dc68e30292327bfe0a18e57209d081..6e59ee96c6b87f0f18f0aa8478436178446934b6 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -57,6 +57,7 @@ describe('Filtered Search Manager', () => {
     input = document.querySelector('.filtered-search');
     tokensContainer = document.querySelector('.tokens-container');
     manager = new gl.FilteredSearchManager();
+    manager.setup();
   });
 
   afterEach(() => {
@@ -72,6 +73,7 @@ describe('Filtered Search Manager', () => {
       spyOn(recentSearchesStoreSrc, 'default');
 
       filteredSearchManager = new gl.FilteredSearchManager();
+      filteredSearchManager.setup();
 
       return filteredSearchManager;
     });
@@ -89,6 +91,7 @@ describe('Filtered Search Manager', () => {
       spyOn(window, 'Flash');
 
       filteredSearchManager = new gl.FilteredSearchManager();
+      filteredSearchManager.setup();
 
       expect(window.Flash).not.toHaveBeenCalled();
     });