diff --git a/app/assets/javascripts/admin/users/components/app.vue b/app/assets/javascripts/admin/users/components/app.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5ff2dbbfcb1ea2eb31bfe4ecba5059f4efb2377c
--- /dev/null
+++ b/app/assets/javascripts/admin/users/components/app.vue
@@ -0,0 +1,21 @@
+<script>
+export default {
+  props: {
+    users: {
+      type: Array,
+      required: false,
+      default: () => [],
+    },
+    paths: {
+      type: Object,
+      required: true,
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <!-- Temporary empty app -->
+  </div>
+</template>
diff --git a/app/assets/javascripts/admin/users/index.js b/app/assets/javascripts/admin/users/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..21780ee99846f76e22e8ba5409c85924b8f0aec7
--- /dev/null
+++ b/app/assets/javascripts/admin/users/index.js
@@ -0,0 +1,22 @@
+import Vue from 'vue';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import AdminUsersApp from './components/app.vue';
+
+export default function(el = document.querySelector('#js-admin-users-app')) {
+  if (!el) {
+    return false;
+  }
+
+  const { users, paths } = el.dataset;
+
+  return new Vue({
+    el,
+    render: createElement =>
+      createElement(AdminUsersApp, {
+        props: {
+          users: convertObjectPropsToCamelCase(JSON.parse(users), { deep: true }),
+          paths: convertObjectPropsToCamelCase(JSON.parse(paths)),
+        },
+      }),
+  });
+}
diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js
index 62a18200b8a9b94ec953b69d07da0f167ae4c25d..07462b4592f680734e85ceb89f0a83b33ecd4882 100644
--- a/app/assets/javascripts/pages/admin/users/index.js
+++ b/app/assets/javascripts/pages/admin/users/index.js
@@ -4,6 +4,7 @@ import Translate from '~/vue_shared/translate';
 import ModalManager from './components/user_modal_manager.vue';
 import csrf from '~/lib/utils/csrf';
 import initConfirmModal from '~/confirm_modal';
+import initAdminUsersApp from '~/admin/users';
 
 const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts';
 const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal';
@@ -56,4 +57,5 @@ document.addEventListener('DOMContentLoaded', () => {
   });
 
   initConfirmModal();
+  initAdminUsersApp();
 });
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 6679b6224ed1613f347bb52dafd1f928227006af..a58f8a6f792719ae35ebf2dd9a60af7cb1ac49f5 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -1,6 +1,13 @@
 # frozen_string_literal: true
 
 module UsersHelper
+  def admin_users_data_attributes(users)
+    {
+      users: Admin::UserSerializer.new.represent(users).to_json,
+      paths: admin_users_paths.to_json
+    }
+  end
+
   def user_link(user)
     link_to(user.name, user_path(user),
             title: user.email,
@@ -208,6 +215,22 @@ def user_display_name(user)
 
   private
 
+  def admin_users_paths
+    {
+      edit: edit_admin_user_path(:id),
+      approve: approve_admin_user_path(:id),
+      reject: reject_admin_user_path(:id),
+      unblock: unblock_admin_user_path(:id),
+      block: block_admin_user_path(:id),
+      deactivate: deactivate_admin_user_path(:id),
+      activate: activate_admin_user_path(:id),
+      unlock: unlock_admin_user_path(:id),
+      delete: admin_user_path(:id),
+      delete_with_contributions: admin_user_path(:id),
+      admin_user: admin_user_path(:id)
+    }
+  end
+
   def blocked_user_badge(user)
     pending_approval_badge = { text: s_('AdminUsers|Pending approval'), variant: 'info' }
     return pending_approval_badge if user.blocked_pending_approval?
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 18f6fd51ca57c8eba9ee3a3c67364cc15ec35f98..731d5ff6746b770296d7d02a47486bdeb363076b 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -72,6 +72,10 @@
 - if @users.empty?
   .nothing-here-block.border-top-0
     = s_('AdminUsers|No users found')
+- elsif Feature.enabled?(:vue_admin_users)
+  #js-admin-users-app{ data: admin_users_data_attributes(@users) }
+    .gl-spinner-container.gl-my-7
+      %span.gl-vertical-align-bottom.gl-spinner.gl-spinner-dark.gl-spinner-lg{ aria: { label: _('Loading') } }
 - else
   .table-holder
     .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' }
diff --git a/config/feature_flags/development/vue_admin_users.yml b/config/feature_flags/development/vue_admin_users.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7464a25c0da342f7fbf6c992c7cad2d66678cab8
--- /dev/null
+++ b/config/feature_flags/development/vue_admin_users.yml
@@ -0,0 +1,8 @@
+---
+name: vue_admin_users
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48922
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290737
+milestone: '13.7'
+type: development
+group: group::compliance
+default_enabled: false
diff --git a/ee/spec/features/admin/admin_users_spec.rb b/ee/spec/features/admin/admin_users_spec.rb
index 84f184fc7766ac7fb34de39b5316c5abf25c22e4..21c314889901c85ca18675e0c9e7b1d0bab07164 100644
--- a/ee/spec/features/admin/admin_users_spec.rb
+++ b/ee/spec/features/admin/admin_users_spec.rb
@@ -12,6 +12,7 @@
   let!(:current_user) { create(:admin, last_activity_on: 5.days.ago) }
 
   before do
+    stub_feature_flags(vue_admin_users: false)
     sign_in(current_user)
     gitlab_enable_admin_mode_sign_in(current_user)
   end
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index 46380218e9154cbe495b4f1e3f3de785987cc130..e7dd50ed514603e792f1f14b92a5a368bea3f0ea 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -9,6 +9,7 @@
   before do
     sign_in(current_user)
     gitlab_enable_admin_mode_sign_in(current_user)
+    stub_feature_flags(vue_admin_users: false)
   end
 
   describe 'GET /admin/users/:id' do
diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb
index afabdcf4fb72e25b31d152aed8cc96f658ceff9f..9482b4f8603b0700d28109636ab3e5c028021864 100644
--- a/spec/features/admin/users/users_spec.rb
+++ b/spec/features/admin/users/users_spec.rb
@@ -15,6 +15,7 @@
 
   describe 'GET /admin/users' do
     before do
+      stub_feature_flags(vue_admin_users: false)
       visit admin_users_path
     end
 
@@ -418,6 +419,7 @@ def expects_warning_to_be_shown
 
   describe 'GET /admin/users/:id/edit' do
     before do
+      stub_feature_flags(vue_admin_users: false)
       visit admin_users_path
       click_link "edit_user_#{user.id}"
     end
diff --git a/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json
new file mode 100644
index 0000000000000000000000000000000000000000..eab8b6268763e93a34f2cdc6f29168b947680361
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/admin_users_data_attributes_paths.json
@@ -0,0 +1,30 @@
+{
+  "type": "object",
+  "properties": {
+    "edit": { "type": "string" },
+    "approve": { "type": "string" },
+    "reject": { "type": "string" },
+    "unblock": { "type": "string" },
+    "block": { "type": "string" },
+    "deactivate": { "type": "string" },
+    "activate": { "type": "string" },
+    "unlock": { "type": "string" },
+    "delete": { "type": "string" },
+    "delete_with_contributions": { "type": "string" },
+    "admin_user": { "type": "string" }
+  },
+  "required": [
+    "edit",
+    "approve",
+    "reject",
+    "unblock",
+    "block",
+    "deactivate",
+    "activate",
+    "unlock",
+    "delete",
+    "delete_with_contributions",
+    "admin_user"
+  ],
+  "additionalProperties": false
+}
diff --git a/spec/frontend/admin/users/index_spec.js b/spec/frontend/admin/users/index_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..171d54c8f4f5d297c1a510dffd99bfeea6104481
--- /dev/null
+++ b/spec/frontend/admin/users/index_spec.js
@@ -0,0 +1,35 @@
+import { createWrapper } from '@vue/test-utils';
+import initAdminUsers from '~/admin/users';
+import AdminUsersApp from '~/admin/users/components/app.vue';
+import { users, paths } from './mock_data';
+
+describe('initAdminUsersApp', () => {
+  let wrapper;
+  let el;
+
+  const findApp = () => wrapper.find(AdminUsersApp);
+
+  beforeEach(() => {
+    el = document.createElement('div');
+    el.setAttribute('data-users', JSON.stringify(users));
+    el.setAttribute('data-paths', JSON.stringify(paths));
+
+    document.body.appendChild(el);
+
+    wrapper = createWrapper(initAdminUsers(el));
+  });
+
+  afterEach(() => {
+    wrapper.destroy();
+    wrapper = null;
+    el.remove();
+    el = null;
+  });
+
+  it('parses and passes props', () => {
+    expect(findApp().props()).toMatchObject({
+      users,
+      paths,
+    });
+  });
+});
diff --git a/spec/frontend/admin/users/mock_data.js b/spec/frontend/admin/users/mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..b80d04454b0cefc948c5654c0151f42a2b248cc5
--- /dev/null
+++ b/spec/frontend/admin/users/mock_data.js
@@ -0,0 +1,29 @@
+export const users = [
+  {
+    id: 2177,
+    name: 'Nikki',
+    createdAt: '2020-11-13T12:26:54.177Z',
+    email: 'nikki@example.com',
+    username: 'nikki',
+    lastActivityOn: null,
+    avatarUrl:
+      'https://secure.gravatar.com/avatar/054f062d8b1a42b123f17e13a173cda8?s=80\\u0026d=identicon',
+    badges: [],
+    projectsCount: 0,
+    actions: [],
+  },
+];
+
+export const paths = {
+  edit: '/admin/users/id/edit',
+  approve: '/admin/users/id/approve',
+  reject: '/admin/users/id/reject',
+  unblock: '/admin/users/id/unblock',
+  block: '/admin/users/id/block',
+  deactivate: '/admin/users/id/deactivate',
+  activate: '/admin/users/id/activate',
+  unlock: '/admin/users/id/unlock',
+  delete: '/admin/users/id',
+  deleteWithContributions: '/admin/users/id',
+  adminUser: '/admin/users/id',
+};
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 5b559e40a80b4cae18b7d3e6f57e0ae7fc80a680..c92c6e6e78eee02eb6805aeab826b95738e623a9 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -333,4 +333,21 @@ def stub_profile_permission_allowed(allowed, current_user = nil)
       allow(helper).to receive(:can?).with(current_user, :read_user_profile, user).and_return(allowed)
     end
   end
+
+  describe '#admin_users_data_attributes' do
+    subject(:data) { helper.admin_users_data_attributes([user]) }
+
+    it 'users matches the serialized json' do
+      entity = double
+      expect_next_instance_of(Admin::UserSerializer) do |instance|
+        expect(instance).to receive(:represent).with([user]).and_return(entity)
+      end
+      expect(entity).to receive(:to_json).and_return("{\"username\":\"admin\"}")
+      expect(data[:users]).to eq "{\"username\":\"admin\"}"
+    end
+
+    it 'paths matches the schema' do
+      expect(data[:paths]).to match_schema('entities/admin_users_data_attributes_paths')
+    end
+  end
 end