diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts
index 69bf95e57cab9..b3e7078d8b5a9 100644
--- a/src/legacy/server/kbn_server.d.ts
+++ b/src/legacy/server/kbn_server.d.ts
@@ -39,6 +39,8 @@ import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/ela
 import { CapabilitiesModifier } from './capabilities';
 import { IndexPatternsServiceFactory } from './index_patterns';
 import { Capabilities } from '../../core/public';
+import { IUiSettingsClient } from '../../legacy/ui/ui_settings/ui_settings_service';
+import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory';
 
 export interface KibanaConfig {
   get<T>(key: string): T;
@@ -77,14 +79,15 @@ declare module 'hapi' {
       name: string,
       factoryFn: (request: Request) => Record<string, any>
     ) => void;
-    uiSettingsServiceFactory: (options: any) => any;
+    uiSettingsServiceFactory: (options?: UiSettingsServiceFactoryOptions) => IUiSettingsClient;
+    logWithMetadata: (tags: string[], message: string, meta: Record<string, any>) => void;
   }
 
   interface Request {
     getSavedObjectsClient(options?: SavedObjectsClientProviderOptions): SavedObjectsClientContract;
     getBasePath(): string;
     getDefaultRoute(): Promise<string>;
-    getUiSettingsService(): any;
+    getUiSettingsService(): IUiSettingsClient;
     getCapabilities(): Promise<Capabilities>;
   }
 
diff --git a/src/legacy/ui/ui_settings/create_objects_client_stub.ts b/src/legacy/ui/ui_settings/create_objects_client_stub.ts
index ebbedb761fae9..ad19b5c8bc7cf 100644
--- a/src/legacy/ui/ui_settings/create_objects_client_stub.ts
+++ b/src/legacy/ui/ui_settings/create_objects_client_stub.ts
@@ -26,6 +26,10 @@ export interface SavedObjectsClientStub {
   update: sinon.SinonStub<any[], any>;
   get: sinon.SinonStub<any[], any>;
   create: sinon.SinonStub<any[], any>;
+  bulkCreate: sinon.SinonStub<any[], any>;
+  bulkGet: sinon.SinonStub<any[], any>;
+  delete: sinon.SinonStub<any[], any>;
+  find: sinon.SinonStub<any[], any>;
   errors: typeof savedObjectsClientErrors;
 }
 
@@ -35,6 +39,10 @@ export function createObjectsClientStub(esDocSource = {}): SavedObjectsClientStu
     get: sinon.stub().returns({ attributes: esDocSource }),
     create: sinon.stub(),
     errors: savedObjectsClientErrors,
+    bulkCreate: sinon.stub(),
+    bulkGet: sinon.stub(),
+    delete: sinon.stub(),
+    find: sinon.stub(),
   };
   return savedObjectsClient;
 }
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts
index 9b9a2fad39aca..654c0fbb66c8b 100644
--- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts
+++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts
@@ -21,9 +21,7 @@ import sinon from 'sinon';
 import expect from '@kbn/expect';
 import Chance from 'chance';
 
-// @ts-ignore
 import * as getUpgradeableConfigNS from './get_upgradeable_config';
-// @ts-ignore
 import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
 
 const chance = new Chance();
@@ -45,7 +43,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() {
         id: options.id,
         version: 'foo',
       })),
-    };
+    } as any; // mute until we have savedObjects mocks
 
     async function run(options = {}) {
       const resp = await createOrUpgradeSavedConfig({
@@ -103,7 +101,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() {
         [chance.word()]: chance.sentence(),
       };
 
-      getUpgradeableConfig.returns({ id: prevVersion, attributes: savedAttributes });
+      getUpgradeableConfig.resolves({
+        id: prevVersion,
+        attributes: savedAttributes,
+        type: '',
+        references: [],
+      });
 
       await run();
 
@@ -125,7 +128,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() {
     it('should log a message for upgrades', async () => {
       const { getUpgradeableConfig, logWithMetadata, run } = setup();
 
-      getUpgradeableConfig.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
+      getUpgradeableConfig.resolves({
+        id: prevVersion,
+        attributes: { buildNum: buildNum - 100 },
+        type: '',
+        references: [],
+      });
 
       await run();
       sinon.assert.calledOnce(logWithMetadata);
@@ -143,7 +151,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() {
     it('does not log when upgrade fails', async () => {
       const { getUpgradeableConfig, logWithMetadata, run, savedObjectsClient } = setup();
 
-      getUpgradeableConfig.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
+      getUpgradeableConfig.resolves({
+        id: prevVersion,
+        attributes: { buildNum: buildNum - 100 },
+        type: '',
+        references: [],
+      });
 
       savedObjectsClient.create.callsFake(async () => {
         throw new Error('foo');
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts
similarity index 55%
rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.js
rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts
index c175e583ee916..0dc3d5f50e97e 100644
--- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.js
+++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts
@@ -18,37 +18,38 @@
  */
 
 import { defaults } from 'lodash';
+import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server';
+import { Legacy } from 'kibana';
 
 import { getUpgradeableConfig } from './get_upgradeable_config';
 
-export async function createOrUpgradeSavedConfig(options) {
-  const {
-    savedObjectsClient,
-    version,
-    buildNum,
-    logWithMetadata,
-    onWriteError,
-  } = options;
+interface Options {
+  savedObjectsClient: SavedObjectsClientContract;
+  version: string;
+  buildNum: number;
+  logWithMetadata: Legacy.Server['logWithMetadata'];
+  onWriteError?: <T extends SavedObjectAttribute = any>(
+    error: Error,
+    attributes: Record<string, any>
+  ) => Record<string, T> | undefined;
+}
+export async function createOrUpgradeSavedConfig<T extends SavedObjectAttribute = any>(
+  options: Options
+): Promise<Record<string, T> | undefined> {
+  const { savedObjectsClient, version, buildNum, logWithMetadata, onWriteError } = options;
 
   // try to find an older config we can upgrade
   const upgradeableConfig = await getUpgradeableConfig({
     savedObjectsClient,
-    version
+    version,
   });
 
   // default to the attributes of the upgradeableConfig if available
-  const attributes = defaults(
-    { buildNum },
-    upgradeableConfig ? upgradeableConfig.attributes : {}
-  );
+  const attributes = defaults({ buildNum }, upgradeableConfig ? upgradeableConfig.attributes : {});
 
   try {
     // create the new SavedConfig
-    await savedObjectsClient.create(
-      'config',
-      attributes,
-      { id: version }
-    );
+    await savedObjectsClient.create('config', attributes, { id: version });
   } catch (error) {
     if (onWriteError) {
       return onWriteError(error, attributes);
@@ -58,9 +59,13 @@ export async function createOrUpgradeSavedConfig(options) {
   }
 
   if (upgradeableConfig) {
-    logWithMetadata(['plugin', 'elasticsearch'], `Upgrade config from ${upgradeableConfig.id} to ${version}`, {
-      prevVersion: upgradeableConfig.id,
-      newVersion: version
-    });
+    logWithMetadata(
+      ['plugin', 'elasticsearch'],
+      `Upgrade config from ${upgradeableConfig.id} to ${version}`,
+      {
+        prevVersion: upgradeableConfig.id,
+        newVersion: version,
+      }
+    );
   }
 }
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts
similarity index 80%
rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.js
rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts
index 1108a01167580..350137a81a49b 100644
--- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.js
+++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.ts
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+import { SavedObjectsClientContract } from 'src/core/server';
 import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
 
 /**
@@ -26,18 +26,22 @@ import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
  *  @property {string} version
  *  @return {Promise<SavedConfig|undefined>}
  */
-export async function getUpgradeableConfig({ savedObjectsClient, version }) {
+export async function getUpgradeableConfig({
+  savedObjectsClient,
+  version,
+}: {
+  savedObjectsClient: SavedObjectsClientContract;
+  version: string;
+}) {
   // attempt to find a config we can upgrade
   const { saved_objects: savedConfigs } = await savedObjectsClient.find({
     type: 'config',
     page: 1,
     perPage: 1000,
     sortField: 'buildNum',
-    sortOrder: 'desc'
+    sortOrder: 'desc',
   });
 
   // try to find a config that we can upgrade
-  return savedConfigs.find(savedConfig => (
-    isConfigVersionUpgradeable(savedConfig.id, version)
-  ));
+  return savedConfigs.find(savedConfig => isConfigVersionUpgradeable(savedConfig.id, version));
 }
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.ts
similarity index 100%
rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.js
rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/index.ts
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts
index 7d5f4e970638d..753e73058af2f 100644
--- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts
+++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts
@@ -24,7 +24,6 @@ import { SavedObjectsClientContract } from 'src/core/server';
 
 import KbnServer from '../../../../server/kbn_server';
 import { createTestServers } from '../../../../../test_utils/kbn_server';
-// @ts-ignore
 import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
 
 describe('createOrUpgradeSavedConfig()', () => {
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts
index 91231da968227..6bb2cb3b87850 100644
--- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts
+++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts
@@ -19,7 +19,6 @@
 
 import expect from '@kbn/expect';
 
-// @ts-ignore
 import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
 // @ts-ignore
 import { pkg } from '../../../utils';
diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.js b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts
similarity index 89%
rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.js
rename to src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts
index beeba6717f24a..8359f02ffee74 100644
--- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.js
+++ b/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts
@@ -20,14 +20,12 @@
 import semver from 'semver';
 const rcVersionRegex = /^(\d+\.\d+\.\d+)\-rc(\d+)$/i;
 
-function extractRcNumber(version) {
+function extractRcNumber(version: string): [string, number] {
   const match = version.match(rcVersionRegex);
-  return match
-    ? [match[1], parseInt(match[2], 10)]
-    : [version, Infinity];
+  return match ? [match[1], parseInt(match[2], 10)] : [version, Infinity];
 }
 
-export function isConfigVersionUpgradeable(savedVersion, kibanaVersion) {
+export function isConfigVersionUpgradeable(savedVersion: string, kibanaVersion: string): boolean {
   if (
     typeof savedVersion !== 'string' ||
     typeof kibanaVersion !== 'string' ||
diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts
index f522f119a26cc..f43c6436d1c33 100644
--- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts
+++ b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts
@@ -23,9 +23,7 @@ import expect from '@kbn/expect';
 // @ts-ignore
 import { Config } from '../../../server/config';
 
-// @ts-ignore
 import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory';
-// @ts-ignore
 import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request';
 // @ts-ignore
 import { uiSettingsMixin } from '../ui_settings_mixin';
@@ -123,7 +121,8 @@ describe('uiSettingsMixin()', () => {
         foo: 'bar',
       });
       sinon.assert.calledOnce(uiSettingsServiceFactoryStub);
-      sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server, {
+      sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server as any, {
+        // @ts-ignore foo doesn't exist on Hapi.Server
         foo: 'bar',
         overrides: {
           foo: 'bar',
@@ -162,7 +161,12 @@ describe('uiSettingsMixin()', () => {
       sinon.assert.notCalled(getUiSettingsServiceForRequestStub);
       const request = {};
       decorations.request.getUiSettingsService.call(request);
-      sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server, request);
+      sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any, {
+        overrides: {
+          foo: 'bar',
+        },
+        getDefaults: sinon.match.func,
+      });
     });
   });
 
diff --git a/src/legacy/ui/ui_settings/routes/delete.js b/src/legacy/ui/ui_settings/routes/delete.ts
similarity index 81%
rename from src/legacy/ui/ui_settings/routes/delete.js
rename to src/legacy/ui/ui_settings/routes/delete.ts
index 78e07bbceab01..7825204e6b99b 100644
--- a/src/legacy/ui/ui_settings/routes/delete.js
+++ b/src/legacy/ui/ui_settings/routes/delete.ts
@@ -16,21 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { Legacy } from 'kibana';
 
-async function handleRequest(request) {
+async function handleRequest(request: Legacy.Request) {
   const { key } = request.params;
   const uiSettings = request.getUiSettingsService();
 
   await uiSettings.remove(key);
   return {
-    settings: await uiSettings.getUserProvided()
+    settings: await uiSettings.getUserProvided(),
   };
 }
 
 export const deleteRoute = {
   path: '/api/kibana/settings/{key}',
   method: 'DELETE',
-  handler: async (request, h) => {
-    return h.response(await handleRequest(request));
-  }
+  handler: async (request: Legacy.Request) => {
+    return await handleRequest(request);
+  },
 };
diff --git a/src/legacy/ui/ui_settings/routes/get.js b/src/legacy/ui/ui_settings/routes/get.ts
similarity index 84%
rename from src/legacy/ui/ui_settings/routes/get.js
rename to src/legacy/ui/ui_settings/routes/get.ts
index 7e91bc46596b5..3e165a12522bb 100644
--- a/src/legacy/ui/ui_settings/routes/get.js
+++ b/src/legacy/ui/ui_settings/routes/get.ts
@@ -16,18 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { Legacy } from 'kibana';
 
-async function handleRequest(request) {
+async function handleRequest(request: Legacy.Request) {
   const uiSettings = request.getUiSettingsService();
   return {
-    settings: await uiSettings.getUserProvided()
+    settings: await uiSettings.getUserProvided(),
   };
 }
 
 export const getRoute = {
   path: '/api/kibana/settings',
   method: 'GET',
-  handler: function (request) {
+  handler(request: Legacy.Request) {
     return handleRequest(request);
-  }
+  },
 };
diff --git a/src/legacy/ui/ui_settings/routes/index.js b/src/legacy/ui/ui_settings/routes/index.ts
similarity index 100%
rename from src/legacy/ui/ui_settings/routes/index.js
rename to src/legacy/ui/ui_settings/routes/index.ts
diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts
index 5b0fbf5a5f256..b076a2a86e166 100644
--- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts
+++ b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts
@@ -23,6 +23,7 @@ import { SavedObjectsClientContract } from 'src/core/server';
 import KbnServer from '../../../../../server/kbn_server';
 import { createTestServers } from '../../../../../../test_utils/kbn_server';
 import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch';
+import { IUiSettingsClient } from '../../../ui_settings_service';
 
 let kbnServer: KbnServer;
 let servers: ReturnType<typeof createTestServers>;
@@ -33,7 +34,7 @@ interface AllServices {
   kbnServer: KbnServer;
   savedObjectsClient: SavedObjectsClientContract;
   callCluster: CallCluster;
-  uiSettings: any;
+  uiSettings: IUiSettingsClient;
   deleteKibanaIndex: typeof deleteKibanaIndex;
 }
 
diff --git a/src/legacy/ui/ui_settings/routes/set.js b/src/legacy/ui/ui_settings/routes/set.ts
similarity index 70%
rename from src/legacy/ui/ui_settings/routes/set.js
rename to src/legacy/ui/ui_settings/routes/set.ts
index e50c9bf08de3e..1f1ab17a0daf7 100644
--- a/src/legacy/ui/ui_settings/routes/set.js
+++ b/src/legacy/ui/ui_settings/routes/set.ts
@@ -16,18 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+import { Legacy } from 'kibana';
 import Joi from 'joi';
 
-async function handleRequest(request) {
+async function handleRequest(request: Legacy.Request) {
   const { key } = request.params;
-  const { value } = request.payload;
+  const { value } = request.payload as any;
   const uiSettings = request.getUiSettingsService();
 
   await uiSettings.set(key, value);
 
   return {
-    settings: await uiSettings.getUserProvided()
+    settings: await uiSettings.getUserProvided(),
   };
 }
 
@@ -36,16 +36,20 @@ export const setRoute = {
   method: 'POST',
   config: {
     validate: {
-      params: Joi.object().keys({
-        key: Joi.string().required(),
-      }).default(),
+      params: Joi.object()
+        .keys({
+          key: Joi.string().required(),
+        })
+        .default(),
 
-      payload: Joi.object().keys({
-        value: Joi.any().required()
-      }).required()
+      payload: Joi.object()
+        .keys({
+          value: Joi.any().required(),
+        })
+        .required(),
     },
-    handler(request) {
+    handler(request: Legacy.Request) {
       return handleRequest(request);
-    }
-  }
+    },
+  },
 };
diff --git a/src/legacy/ui/ui_settings/routes/set_many.js b/src/legacy/ui/ui_settings/routes/set_many.ts
similarity index 73%
rename from src/legacy/ui/ui_settings/routes/set_many.js
rename to src/legacy/ui/ui_settings/routes/set_many.ts
index 8e7882f48ef70..18b1046417fec 100644
--- a/src/legacy/ui/ui_settings/routes/set_many.js
+++ b/src/legacy/ui/ui_settings/routes/set_many.ts
@@ -16,17 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+import { Legacy } from 'kibana';
 import Joi from 'joi';
 
-async function handleRequest(request) {
-  const { changes } = request.payload;
+async function handleRequest(request: Legacy.Request) {
+  const { changes } = request.payload as any;
   const uiSettings = request.getUiSettingsService();
 
   await uiSettings.setMany(changes);
 
   return {
-    settings: await uiSettings.getUserProvided()
+    settings: await uiSettings.getUserProvided(),
   };
 }
 
@@ -35,12 +35,16 @@ export const setManyRoute = {
   method: 'POST',
   config: {
     validate: {
-      payload: Joi.object().keys({
-        changes: Joi.object().unknown(true).required()
-      }).required()
+      payload: Joi.object()
+        .keys({
+          changes: Joi.object()
+            .unknown(true)
+            .required(),
+        })
+        .required(),
     },
-    handler(request) {
+    handler(request: Legacy.Request) {
       return handleRequest(request);
-    }
-  }
+    },
+  },
 };
diff --git a/src/legacy/ui/ui_settings/ui_settings_service.mock.ts b/src/legacy/ui/ui_settings/ui_settings_service.mock.ts
new file mode 100644
index 0000000000000..7c1a17ebd447c
--- /dev/null
+++ b/src/legacy/ui/ui_settings/ui_settings_service.mock.ts
@@ -0,0 +1,40 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { IUiSettingsClient } from './ui_settings_service';
+
+const createServiceMock = () => {
+  const mocked: jest.Mocked<IUiSettingsClient> = {
+    getDefaults: jest.fn(),
+    get: jest.fn(),
+    getAll: jest.fn(),
+    getUserProvided: jest.fn(),
+    setMany: jest.fn(),
+    set: jest.fn(),
+    remove: jest.fn(),
+    removeMany: jest.fn(),
+    isOverridden: jest.fn(),
+  };
+  mocked.get.mockResolvedValue(false);
+  return mocked;
+};
+
+export const uiSettingsServiceMock = {
+  create: createServiceMock,
+};
diff --git a/src/legacy/ui/ui_settings/ui_settings_service.test.ts b/src/legacy/ui/ui_settings/ui_settings_service.test.ts
index bb407d7b3a91b..f37076b27ad6f 100644
--- a/src/legacy/ui/ui_settings/ui_settings_service.test.ts
+++ b/src/legacy/ui/ui_settings/ui_settings_service.test.ts
@@ -21,9 +21,7 @@ import expect from '@kbn/expect';
 import Chance from 'chance';
 import sinon from 'sinon';
 
-// @ts-ignore
 import { UiSettingsService } from './ui_settings_service';
-// @ts-ignore
 import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config';
 import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub';
 
@@ -43,7 +41,7 @@ describe('ui settings', () => {
   const sandbox = sinon.createSandbox();
 
   function setup(options: SetupOptions = {}) {
-    const { getDefaults, defaults = {}, overrides, esDocSource = {} } = options;
+    const { getDefaults, defaults = {}, overrides = {}, esDocSource = {} } = options;
 
     const savedObjectsClient = createObjectsClientStub(esDocSource);
 
@@ -233,7 +231,7 @@ describe('ui settings', () => {
       });
 
       try {
-        await uiSettings.setMany(['bar', 'foo']);
+        await uiSettings.setMany({ baz: 'baz', foo: 'foo' });
       } catch (error) {
         expect(error.message).to.be('Unable to update "foo" because it is overridden');
       }
@@ -489,7 +487,7 @@ describe('ui settings', () => {
     it('pulls user configuration from ES', async () => {
       const esDocSource = {};
       const { uiSettings, assertGetQuery } = setup({ esDocSource });
-      await uiSettings.get();
+      await uiSettings.get('any');
       assertGetQuery();
     });
 
diff --git a/src/legacy/ui/ui_settings/ui_settings_service.js b/src/legacy/ui/ui_settings/ui_settings_service.ts
similarity index 52%
rename from src/legacy/ui/ui_settings/ui_settings_service.js
rename to src/legacy/ui/ui_settings/ui_settings_service.ts
index 9f79ed2dbe168..57312140b16b3 100644
--- a/src/legacy/ui/ui_settings/ui_settings_service.js
+++ b/src/legacy/ui/ui_settings/ui_settings_service.ts
@@ -16,28 +16,77 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+import { Legacy } from 'kibana';
 import { defaultsDeep } from 'lodash';
 import Boom from 'boom';
 
+import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server';
 import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
 
+export interface UiSettingsServiceOptions {
+  type: string;
+  id: string;
+  buildNum: number;
+  savedObjectsClient: SavedObjectsClientContract;
+  overrides?: Record<string, SavedObjectAttribute>;
+  getDefaults?: () => Record<string, UiSettingsParams>;
+  logWithMetadata?: Legacy.Server['logWithMetadata'];
+}
+
+interface ReadOptions {
+  ignore401Errors?: boolean;
+  autoCreateOrUpgradeIfMissing?: boolean;
+}
+
+interface UserProvidedValue {
+  userValue?: SavedObjectAttribute;
+  isOverridden?: boolean;
+}
+
+type UiSettingsRawValue = UiSettingsParams & UserProvidedValue;
+
+type UserProvided = Record<string, UserProvidedValue>;
+type UiSettingsRaw = Record<string, UiSettingsRawValue>;
+
+type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string';
+
+interface UiSettingsParams {
+  name: string;
+  value: SavedObjectAttribute;
+  description: string;
+  category: string[];
+  options?: string[];
+  optionLabels?: Record<string, string>;
+  requiresPageReload?: boolean;
+  readonly?: boolean;
+  type?: UiSettingsType;
+}
+
+export interface IUiSettingsClient {
+  getDefaults: () => Promise<Record<string, UiSettingsParams>>;
+  get: <T extends SavedObjectAttribute = any>(key: string) => Promise<T>;
+  getAll: <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>>;
+  getUserProvided: () => Promise<UserProvided>;
+  setMany: <T extends SavedObjectAttribute = any>(changes: Record<string, T>) => Promise<void>;
+  set: <T extends SavedObjectAttribute = any>(key: string, value: T) => Promise<void>;
+  remove: (key: string) => Promise<void>;
+  removeMany: (keys: string[]) => Promise<void>;
+  isOverridden: (key: string) => boolean;
+}
 /**
  *  Service that provides access to the UiSettings stored in elasticsearch.
  *  @class UiSettingsService
  */
-export class UiSettingsService {
-  /**
-   *  @constructor
-   *  @param {Object} options
-   *  @property {string} options.type type of SavedConfig object
-   *  @property {string} options.id id of SavedConfig object
-   *  @property {number} options.buildNum
-   *  @property {SavedObjectsClient} options.savedObjectsClient
-   *  @property {Function} [options.getDefaults]
-   *  @property {Function} [options.log]
-   */
-  constructor(options) {
+export class UiSettingsService implements IUiSettingsClient {
+  private readonly _type: UiSettingsServiceOptions['type'];
+  private readonly _id: UiSettingsServiceOptions['id'];
+  private readonly _buildNum: UiSettingsServiceOptions['buildNum'];
+  private readonly _savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient'];
+  private readonly _overrides: NonNullable<UiSettingsServiceOptions['overrides']>;
+  private readonly _getDefaults: NonNullable<UiSettingsServiceOptions['getDefaults']>;
+  private readonly _logWithMetadata: NonNullable<UiSettingsServiceOptions['logWithMetadata']>;
+
+  constructor(options: UiSettingsServiceOptions) {
     const {
       type,
       id,
@@ -65,36 +114,38 @@ export class UiSettingsService {
   }
 
   // returns a Promise for the value of the requested setting
-  async get(key) {
+  async get<T extends SavedObjectAttribute = any>(key: string): Promise<T> {
     const all = await this.getAll();
     return all[key];
   }
 
-  async getAll() {
+  async getAll<T extends SavedObjectAttribute = any>() {
     const raw = await this.getRaw();
 
-    return Object.keys(raw)
-      .reduce((all, key) => {
+    return Object.keys(raw).reduce(
+      (all, key) => {
         const item = raw[key];
-        const hasUserValue = 'userValue' in item;
-        all[key] = hasUserValue ? item.userValue : item.value;
+        all[key] = ('userValue' in item ? item.userValue : item.value) as T;
         return all;
-      }, {});
+      },
+      {} as Record<string, T>
+    );
   }
 
-  async getRaw() {
+  // NOTE: should be a private method
+  async getRaw(): Promise<UiSettingsRaw> {
     const userProvided = await this.getUserProvided();
     return defaultsDeep(userProvided, await this.getDefaults());
   }
 
-  async getUserProvided(options) {
-    const userProvided = {};
+  async getUserProvided(options: ReadOptions = {}): Promise<UserProvided> {
+    const userProvided: UserProvided = {};
 
     // write the userValue for each key stored in the saved object that is not overridden
     for (const [key, userValue] of Object.entries(await this._read(options))) {
       if (userValue !== null && !this.isOverridden(key)) {
         userProvided[key] = {
-          userValue
+          userValue,
         };
       }
     }
@@ -102,45 +153,51 @@ export class UiSettingsService {
     // write all overridden keys, dropping the userValue is override is null and
     // adding keys for overrides that are not in saved object
     for (const [key, userValue] of Object.entries(this._overrides)) {
-      userProvided[key] = userValue === null
-        ? { isOverridden: true }
-        : { isOverridden: true, userValue };
+      userProvided[key] =
+        userValue === null ? { isOverridden: true } : { isOverridden: true, userValue };
     }
 
     return userProvided;
   }
 
-  async setMany(changes) {
+  async setMany<T extends SavedObjectAttribute = any>(changes: Record<string, T>) {
     await this._write({ changes });
   }
 
-  async set(key, value) {
+  async set<T extends SavedObjectAttribute = any>(key: string, value: T) {
     await this.setMany({ [key]: value });
   }
 
-  async remove(key) {
+  async remove(key: string) {
     await this.set(key, null);
   }
 
-  async removeMany(keys) {
-    const changes = {};
+  async removeMany(keys: string[]) {
+    const changes: Record<string, null> = {};
     keys.forEach(key => {
       changes[key] = null;
     });
     await this.setMany(changes);
   }
 
-  isOverridden(key) {
+  isOverridden(key: string) {
     return this._overrides.hasOwnProperty(key);
   }
 
-  assertUpdateAllowed(key) {
+  // NOTE: should be private method
+  assertUpdateAllowed(key: string) {
     if (this.isOverridden(key)) {
       throw Boom.badRequest(`Unable to update "${key}" because it is overridden`);
     }
   }
 
-  async _write({ changes, autoCreateOrUpgradeIfMissing = true }) {
+  private async _write<T extends SavedObjectAttribute = any>({
+    changes,
+    autoCreateOrUpgradeIfMissing = true,
+  }: {
+    changes: Record<string, T>;
+    autoCreateOrUpgradeIfMissing?: boolean;
+  }) {
     for (const key of Object.keys(changes)) {
       this.assertUpdateAllowed(key);
     }
@@ -162,72 +219,77 @@ export class UiSettingsService {
 
       await this._write({
         changes,
-        autoCreateOrUpgradeIfMissing: false
+        autoCreateOrUpgradeIfMissing: false,
       });
     }
   }
 
-  async _read(options = {}) {
-    const {
-      ignore401Errors = false,
-      autoCreateOrUpgradeIfMissing = true
-    } = options;
-
+  private async _read<T extends SavedObjectAttribute>({
+    ignore401Errors = false,
+    autoCreateOrUpgradeIfMissing = true,
+  }: ReadOptions = {}): Promise<Record<string, T>> {
     const {
       isConflictError,
       isNotFoundError,
       isForbiddenError,
-      isEsUnavailableError,
       isNotAuthorizedError,
     } = this._savedObjectsClient.errors;
 
-    const isIgnorableError = error => (
-      isForbiddenError(error) ||
-      isEsUnavailableError(error) ||
-      (ignore401Errors && isNotAuthorizedError(error))
-    );
-
     try {
       const resp = await this._savedObjectsClient.get(this._type, this._id);
       return resp.attributes;
     } catch (error) {
       if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) {
-        const failedUpgradeAttributes = await createOrUpgradeSavedConfig({
+        const failedUpgradeAttributes = await createOrUpgradeSavedConfig<T>({
           savedObjectsClient: this._savedObjectsClient,
           version: this._id,
           buildNum: this._buildNum,
           logWithMetadata: this._logWithMetadata,
-          async onWriteError(error, attributes) {
-            if (isConflictError(error)) {
+          onWriteError(writeError, attributes) {
+            if (isConflictError(writeError)) {
               // trigger `!failedUpgradeAttributes` check below, since another
               // request caused the uiSettings object to be created so we can
               // just re-read
-              return false;
+              return;
             }
 
-            if (isNotAuthorizedError(error) || isForbiddenError(error)) {
+            if (isNotAuthorizedError(writeError) || isForbiddenError(writeError)) {
               return attributes;
             }
 
-            throw error;
-          }
+            throw writeError;
+          },
         });
 
         if (!failedUpgradeAttributes) {
           return await this._read({
-            ...options,
-            autoCreateOrUpgradeIfMissing: false
+            ignore401Errors,
+            autoCreateOrUpgradeIfMissing: false,
           });
         }
 
         return failedUpgradeAttributes;
       }
 
-      if (isIgnorableError(error)) {
+      if (this.isIgnorableError(error, ignore401Errors)) {
         return {};
       }
 
       throw error;
     }
   }
+
+  private isIgnorableError(error: Error, ignore401Errors: boolean) {
+    const {
+      isForbiddenError,
+      isEsUnavailableError,
+      isNotAuthorizedError,
+    } = this._savedObjectsClient.errors;
+
+    return (
+      isForbiddenError(error) ||
+      isEsUnavailableError(error) ||
+      (ignore401Errors && isNotAuthorizedError(error))
+    );
+  }
 }
diff --git a/src/legacy/ui/ui_settings/ui_settings_service_factory.js b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts
similarity index 59%
rename from src/legacy/ui/ui_settings/ui_settings_service_factory.js
rename to src/legacy/ui/ui_settings/ui_settings_service_factory.ts
index f83a0d9825557..9e1384494161c 100644
--- a/src/legacy/ui/ui_settings/ui_settings_service_factory.js
+++ b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts
@@ -16,29 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { Legacy } from 'kibana';
+import {
+  IUiSettingsClient,
+  UiSettingsService,
+  UiSettingsServiceOptions,
+} from './ui_settings_service';
 
-import { UiSettingsService } from './ui_settings_service';
-
+export type UiSettingsServiceFactoryOptions = Pick<
+  UiSettingsServiceOptions,
+  'savedObjectsClient' | 'getDefaults' | 'overrides'
+>;
 /**
  *  Create an instance of UiSettingsService that will use the
- *  passed `callCluster` function to communicate with elasticsearch
+ *  passed `savedObjectsClient` to communicate with elasticsearch
  *
- *  @param {Hapi.Server} server
- *  @param {Object} options
- *  @property {AsyncFunction} options.callCluster function that accepts a method name and
- *                            param object which causes a request via some elasticsearch client
- *  @property {AsyncFunction} [options.getDefaults] async function that returns defaults/details about
- *                            the uiSettings.
- *  @return {UiSettingsService}
+ *  @return {IUiSettingsClient}
  */
-export function uiSettingsServiceFactory(server, options) {
+export function uiSettingsServiceFactory(
+  server: Legacy.Server,
+  options: UiSettingsServiceFactoryOptions
+): IUiSettingsClient {
   const config = server.config();
 
-  const {
-    savedObjectsClient,
-    getDefaults,
-    overrides,
-  } = options;
+  const { savedObjectsClient, getDefaults, overrides } = options;
 
   return new UiSettingsService({
     type: 'config',
@@ -47,6 +48,6 @@ export function uiSettingsServiceFactory(server, options) {
     savedObjectsClient,
     getDefaults,
     overrides,
-    logWithMetadata: (...args) => server.logWithMetadata(...args),
+    logWithMetadata: server.logWithMetadata,
   });
 }
diff --git a/src/legacy/ui/ui_settings/ui_settings_service_for_request.js b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts
similarity index 74%
rename from src/legacy/ui/ui_settings/ui_settings_service_for_request.js
rename to src/legacy/ui/ui_settings/ui_settings_service_for_request.ts
index 422c9cc14f833..e265ad5f1e115 100644
--- a/src/legacy/ui/ui_settings/ui_settings_service_for_request.js
+++ b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts
@@ -17,8 +17,11 @@
  * under the License.
  */
 
+import { Legacy } from 'kibana';
 import { uiSettingsServiceFactory } from './ui_settings_service_factory';
+import { IUiSettingsClient, UiSettingsServiceOptions } from './ui_settings_service';
 
+type Options = Pick<UiSettingsServiceOptions, 'getDefaults' | 'overrides'>;
 /**
  *  Get/create an instance of UiSettingsService bound to a specific request.
  *  Each call is cached (keyed on the request object itself) and subsequent
@@ -28,20 +31,20 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory';
  *  @param {Hapi.Server} server
  *  @param {Hapi.Request} request
  *  @param {Object} [options={}]
- *  @property {AsyncFunction} [options.getDefaults] async function that returns defaults/details about
- *                            the uiSettings.
- *  @return {UiSettingsService}
+
+ *  @return {IUiSettingsClient}
  */
-export function getUiSettingsServiceForRequest(server, request, options = {}) {
-  const {
-    getDefaults,
-    overrides,
-  } = options;
+export function getUiSettingsServiceForRequest(
+  server: Legacy.Server,
+  request: Legacy.Request,
+  options: Options
+): IUiSettingsClient {
+  const { getDefaults, overrides } = options;
 
   const uiSettingsService = uiSettingsServiceFactory(server, {
     getDefaults,
     overrides,
-    savedObjectsClient: request.getSavedObjectsClient()
+    savedObjectsClient: request.getSavedObjectsClient(),
   });
 
   return uiSettingsService;
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts
index bd45cec316dfc..91809fbaede03 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -6,6 +6,7 @@
 
 import { Legacy } from 'kibana';
 import { setupRequest } from './setup_request';
+import { uiSettingsServiceMock } from '../../../../../../../src/legacy/ui/ui_settings/ui_settings_service.mock';
 
 function getMockRequest() {
   const callWithRequestSpy = jest.fn();
@@ -125,8 +126,10 @@ describe('setupRequest', () => {
     it('should set `ignore_throttled=true` if `includeFrozen=false`', async () => {
       const { mockRequest, callWithRequestSpy } = getMockRequest();
 
+      const uiSettingsService = uiSettingsServiceMock.create();
       // mock includeFrozen to return false
-      mockRequest.getUiSettingsService = () => ({ get: async () => false });
+      uiSettingsService.get.mockResolvedValue(false);
+      mockRequest.getUiSettingsService = () => uiSettingsService;
       const { client } = await setupRequest(mockRequest);
       await client.search({});
       const params = callWithRequestSpy.mock.calls[0][2];
@@ -136,8 +139,10 @@ describe('setupRequest', () => {
     it('should set `ignore_throttled=false` if `includeFrozen=true`', async () => {
       const { mockRequest, callWithRequestSpy } = getMockRequest();
 
+      const uiSettingsService = uiSettingsServiceMock.create();
       // mock includeFrozen to return true
-      mockRequest.getUiSettingsService = () => ({ get: async () => true });
+      uiSettingsService.get.mockResolvedValue(true);
+      mockRequest.getUiSettingsService = () => uiSettingsService;
       const { client } = await setupRequest(mockRequest);
       await client.search({});
       const params = callWithRequestSpy.mock.calls[0][2];