From f7b5144e1d991e1b679dbeed168a41648c571dd0 Mon Sep 17 00:00:00 2001
From: Constance <constancecchen@users.noreply.github.com>
Date: Thu, 9 Jul 2020 13:10:31 -0700
Subject: [PATCH] New Enterprise Search Kibana plugin (#66922)

* Initial App Search in Kibana plugin work

- Initializes a new platform plugin that ships out of the box w/ x-pack
- Contains a very basic front-end that shows AS engines, error states, or a Setup Guide
- Contains a very basic server that remotely calls the AS internal engines API and returns results

* Update URL casing to match Kibana best practices

- URL casing appears to be snake_casing, but kibana.json casing appears to be camelCase

* Register App Search plugin in Home Feature Catalogue

* Add custom App Search in Kibana logo

- I haven't had much success in surfacing a SVG file via a server-side endpoint/URL, but then I realized EuiIcon supports passing in a ReactElement directly. Woo!

* Fix appSearch.host config setting to be optional

- instead of crashing folks on load

* Rename plugin to Enterprise Search

- per product decision, URL should be enterprise_search/app_search and Workplace Search should also eventually live here
- reorganize folder structure in anticipation for another workplace_search plugin/codebase living alongside app_search
- rename app.tsx/main.tsx to a standard top-level index.tsx (which will contain top-level routes/state)
- rename AS->ES files/vars where applicable
- TODO: React Router

* Set up React Router URL structure

* Convert showSetupGuide action/flag to a React Router link

- remove showSetupGuide flag
- add a new shared helper component for combining EuiButton/EuiLink with React Router behavior (https://github.com/elastic/eui/blob/master/wiki/react-router.md#react-router-51)

* Implement Kibana Chrome breadcrumbs

- create shared helper (WS will presumably also want this) for generating EUI breadcrumb objects with React Router links+click behavior
- create React component that calls chrome.setBreadcrumbs on page mount
- clean up type definitions - move app-wide props to IAppSearchProps and update most pages/views to simply import it instead of calling their own definitions

* Added server unit tests (#2)

* Added unit test for server

* PR Feedback

* Refactor top-level Kibana props to a global context state

- rather them passing them around verbosely as props, the components that need them should be able to call the useContext hook

+ Remove IAppSearchProps in favor of IKibanaContext

+ Also rename `appSearchUrl` to `enterpriseSearchUrl`, since this context will contained shared/Kibana-wide values/actions useful to both AS and WS

* Added unit tests for public (#4)

* application.test.ts

* Added Unit Test for EngineOverviewHeader

* Added Unit Test for generate_breadcrumbs

* Added Unit Test for set_breadcrumb.tsx

* Added a unit test for link_events

- Also changed link_events.tsx to link_events.ts since it's just TS, no
React
- Modified letBrowserHandleEvent so it will still return a false
boolean when target is blank

* Betterize these tests

Co-Authored-By: Constance <constancecchen@users.noreply.github.com>

Co-authored-by: Constance <constancecchen@users.noreply.github.com>

* Add UI telemetry tracking to AS in Kibana (#5)

* Set up Telemetry usageCollection, savedObjects, route, & shared helper

- The Kibana UsageCollection plugin handles collecting our telemetry UI data (views, clicks, errors, etc.) and pushing it to elastic's telemetry servers
- That data is stored in incremented in Kibana's savedObjects lib/plugin (as well as mapped)
- When an end-user hits a certain view or action, the shared helper will ping the app search telemetry route which increments the savedObject store

* Update client-side views/links to new shared telemetry helper

* Write tests for new telemetry files

* Implement remaining unit tests (#7)

* Write tests for React Router+EUI helper components

* Update generate_breadcrumbs test

- add test suite for generateBreadcrumb() itself (in order to cover a missing branch)
- minor lint fixes
- remove unnecessary import from set_breadcrumbs test

* Write test for get_username util

+ update test to return a more consistent falsey value (null)

* Add test for SetupGuide

* [Refactor] Pull out various Kibana context mocks into separate files

- I'm creating a reusable useContext mock for shallow()ed enzyme components
+ add more documentation comments + examples

* Write tests for empty state components

+ test new usecontext shallow mock

* Empty state components: Add extra getUserName branch test

* Write test for app search index/routes

* Write tests for engine overview table

+ fix bonus bug

* Write Engine Overview tests

+ Update EngineOverview logic to account for issues found during tests :)
  - Move http to async/await syntax instead of promise syntax (works better with existing HttpServiceMock jest.fn()s)
  - hasValidData wasn't strict enough in type checking/object nest checking and was causing the app itself to crash (no bueno)

* Refactor EngineOverviewHeader test to use shallow + to full coverage

- missed adding this test during telemetry work
- switching to shallow and beforeAll reduces the test time from 5s to 4s!

* [Refactor] Pull out React Router history mocks into a test util helper

+ minor refactors/updates

* Add small tests to increase branch coverage

- mostly testing fallbacks or removing fallbacks in favor of strict type interface
- these are slightly obsessive so I'd also be fine ditching them if they aren't terribly valuable

* Address larger tech debt/TODOs (#8)

* Fix optional chaining TODO

- turns out my local Prettier wasn't up to date, completely my bad

* Fix constants TODO

- adds a common folder/architecture for others to use in the future

* Remove TODO for eslint-disable-line and specify lint rule being skipped

- hopefully that's OK for review, I can't think of any other way to sanely do this without re-architecting the entire file or DDoSing our API

* Add server-side logging to route dependencies

+ add basic example of error catching/logging to Telemetry route
+ [extra] refactor mockResponseFactory name to something slightly easier to read

* Move more Engines Overview API logic/logging to server-side

- handle data validation in the server-side
- wrap server-side API in a try/catch to account for fetch issues
- more correctly return 2xx/4xx statuses and more correctly deal with those responses in the front-end
- Add server info/error/debug logs (addresses TODO)
- Update tests + minor refactors/cleanup
    - remove expectResponseToBe200With helper (since we're now returning multiple response types) and instead make mockResponse var name more readable
    - one-line header auth
    - update tests with example error logs
    - update schema validation for `type` to be an enum of `indexed`/`meta` (more accurately reflecting API)

* Per telemetry team feedback, rename usageCollection telemetry mapping name to simpler 'app_search'

- since their mapping already nests under 'kibana.plugins'
- note: I left the savedObjects name with the '_telemetry' suffix, as there very well may be a use case for top-level generic 'app_search' saved objects

* Update Setup Guide installation instructions (#9)

Co-authored-by: Chris Cressman <chris@chriscressman.com>

* [Refactor] DRY out route test helper

* [Refactor] Rename public/test_utils to public/__mocks__

- to better follow/use jest setups and for .mock.ts suffixes

* Add platinum licensing check to Meta Engines table/call (#11)

* Licensing plugin setup

* Add LicensingContext setup

* Update EngineOverview to not hit meta engines API on platinum license

* Add Jest test helpers for future shallow/context use

* Update plugin to use new Kibana nav + URL update (#12)

* Update new nav categories to add Enterprise Search + update plugin to use new category

- per @johnbarrierwilson and Matt Riley, Enterprise Search should be under Kibana and above Observability
- Run `node scripts/check_published_api_changes.js --accept` since this new category affects public API

* [URL UPDATE] Change '/app/enterprise_search/app_search' to '/app/app_search'

- This needs to be done because App Search and Workplace search *have* to be registered as separate plugins to have 2 distinct nav links
- Currently Kibana doesn't support nested app names (see: https://github.com/elastic/kibana/issues/59190) but potentially will in the future

- To support this change, we need to update applications/index.tsx to NOT handle '/app/enterprise_search' level routing, but instead accept an async imported app component (e.g. AppSearch, WorkplaceSearch).
- AppSearch should now treat its router as root '/' instead of '/app_search'

- (Addl) Per Josh Dover's recommendation, switch to `<Router history={params.history}>` from `<BrowserRouter basename={params.appBasePath}>` since they're deprecating appBasePath

* Update breadcrumbs helper to account for new URLs

- Remove path for Enterprise Search breadcrumb, since '/app/enterprise_search' will not link anywhere meaningful for the foreseeable future, so the Enterprise Search root should not go anywhere
- Update App Search helper to go to root path, per new React Router setup

Test changes:
- Mock custom basepath for App Search tests
- Swap enterpriseSearchBreadcrumbs and appSearchBreadcrumbs test order (since the latter overrides the default mock)

* Add create_first_engine_button telemetry tracking to EmptyState

* Switch plugin URLs back to /app/enterprise_search/app_search

Now that https://github.com/elastic/kibana/pull/66455 has been merged in :tada:

* Add i18n formatted messages / translations (#13)

* Add i18n provider and formatted/i18n translated messages

* Update tests to account for new I18nProvider context + FormattedMessage components

- Add new mountWithContext helper that provides all contexts+providers used in top-level app
- Add new shallowWithIntl helper for shallow() components that dive into FormattedMessage

* Format i18n dates and numbers

+ update some mock tests to not throw react-intl invalid date messages

* Update EngineOverviewHeader to disable button on prop

* Address review feedback (#14)

* Fix Prettier linting issues

* Escape App Search API endpoint URLs

- per PR feedback
- querystring should automatically encodeURIComponent / escape query param strings

* Update server plugin.ts to use getStartServices() rather than storing local references from start()

- Per feedback: https://github.com/elastic/kibana/blob/master/src/core/CONVENTIONS.md#applications

- Note: savedObjects.registerType needs to be outside of getStartServices, or an error is thrown

- Side update to registerTelemetryUsageCollector to simplify args

- Update/fix tests to account for changes

* E2E testing (#6)

* Wired up basics for E2E testing

* Added version with App Search

* Updated naming

* Switched configuration around

* Added concept of 'fixtures'

* Figured out how to log in as the enterprise_search user

* Refactored to use an App Search service

* Added some real tests

* Added a README

* Cleanup

* More cleanup

* Error handling + README updatre

* Removed unnecessary files

* Apply suggestions from code review

Co-authored-by: Constance <constancecchen@users.noreply.github.com>

* Update x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx

Co-authored-by: Constance <constancecchen@users.noreply.github.com>

* PR feedback - updated README

* Additional lint fixes

Co-authored-by: Constance <constancecchen@users.noreply.github.com>

* Add README and CODEOWNERS (#15)

* Add plugin README and CODEOWNERS

* Fix Typescript errors (#16)

* Fix public mocks

* Fix empty states types

* Fix engine table component errors

* Fix engine overview component errors

* Fix setup guide component errors

- SetBreadcrumbs will be fixed in a separate commit

* Fix App Search index errors

* Fix engine overview header component errors

* Fix applications context index errors

* Fix kibana breadcrumb helper errors

* Fix license helper errors

* :exclamation: Refactor React Router EUI link/button helpers
- in order to fix typescript errors

- this changes the component logic significantly to a react render prop, so that the Link and Button components can have different types - however, end behavior should still remain the same

* Fix telemetry helper errors

* Minor unused var cleanup in plugin files

* Fix telemetry collector/savedobjects errors

* Fix MockRouter type errors and add IRouteDependencies export

- routes will use IRouteDependencies in the next few commits

* Fix engines route errors

* Fix telemetry route errors

* Remove any type from source code

- thanks to Scotty for the inspiration

* Add eslint rules for Enterprise Search plugin

- Add checks for type any, but only on non-test files
- Disable react-hooks/exhaustive-deps, since we're already disabling it in a few files and other plugins also have it turned off

* Cover uncovered lines in engines_table and telemetry tests

* Fixed TS warnings in E2E tests (#17)

* Feedback: Convert static CSS values to EUI variables where possible

* Feedback: Flatten nested CSS where possible

- Prefer setting CSS class overrides on individual EUI components, not on a top-level page

+ Change CSS class casing from kebab-case to camelCase to better match EUI/Kibana

+ Remove unnecessary .euiPageContentHeader margin-bottom override by changing the panelPaddingSize of euiPageContent

+ Decrease engine overview table padding on mobile

* Refactor out components shared with Workplace Search (#18)

* Move getUserName helper to shared

- in preparation for Workplace Search plugin also using this helper

* Move Setup Guide layout to a shared component

* Setup Guide: add extra props for standard/native auth links

Note: It's possible this commit may be unnecessary if we can publish shared Enterprise Search security mode docs

* Update copy per feedback from copy team

* Address various telemetry issues

- saved objects: removing indexing per #43673
- add schema and generate json per #64942
- move definitions over to collectors since saved objects is mostly empty at this point, and schema throws an error when it imports an obj instead of being defined inline
- istanbul ignore saved_objects file since it doesn't have anything meaningful to test but was affecting code coverage

* Disable plugin access if a normal user does not have access to App Search (#19)

* Set up new server security dependency and configs

* Set up access capabilities

* Set up checkAccess helper/caller

* Remove NoUserState component from the public UI

- Since this is now being handled by checkAccess / normal users should never see the plugin at all if they don't have an account/access, the component is no longer needed

* Update server routes to account for new changes

- Remove login redirect catch from routes, since the access helper should now handle that for most users by disabling the plugin (superusers will see a generic cannot connect/error screen)
- Refactor out new config values to a shared mock

* Refactor Enterprise Search http call to hit/return new internal API endpoint

+ pull out the http call to a separate library for upcoming public URL work (so that other files can call it directly as well)

* [Discussion] Increase timeout but add another warning timeout for slow servers

- per recommendation/convo with Brandon

* Register feature control

* Remove no_as_account from UI telemetry

- since we're no longer tracking that in the UI

* Address PR feedback - isSuperUser check

* Public URL support for Elastic Cloud (#21)

* Add server-side public URL route

- Per feedback from Kibana platform team, it's not possible to pass info from server/ to public/ without a HTTP call :[

* Update MockRouter for routes without any payload/params

* Add client-side helper for calling the new public URL API

+ API seems to return a URL a trailing slash, which we need to omit

* Update public/plugin.ts to check and set a public URL

- relies on this.hasCheckedPublicUrl to only make the call once per page load instead of on every page nav

* Fix failing feature control tests

- Split up scenario cases as needed
- Add plugin as an exception alongside ML & Monitoring

* Address PR feedback

- version: kibana
- copy edits
- Sass vars
- code cleanup

* Casing feedback: change all plugin registration IDs from snake_case to camelCase

- note: current remainng snake_case exceptions are telemetry keys
- file names and api endpoints are snake_case per conventions

* Misc security feedback

- remove set
- remove unnecessary capabilities registration
- telemetry namespace agnostic

* Security feedback: add warn logging to telemetry collector

see https://github.com/elastic/kibana/pull/66922#discussion_r451215760
- add if statement
- pass log dependency around (this is kinda medium, should maybe refactor)
- update tests
- move test file comment to the right file (was meant for telemetry route file)

* Address feedback from Pierre

- Remove unnecessary ServerConfigType
- Remove unnecessary uiCapabilities
- Move registerTelemetryRoute / SavedObjectsServiceStart workaround
- Remove unnecessary license optional chaining

* PR feedback

Address type/typos

* Fix telemetry API call returning 415 on Chrome

- I can't even?? I swear charset=utf-8 fixed the same error a few weeks ago

* Fix failing tests

* Update Enterprise Search functional tests (without host) to run on CI

- Fix incorrect navigateToApp slug (hadn't realized this was a URL, not an ID)
- Update without_host_configured tests to run without API key
- Update README

* Address PR feedback from Pierre

- remove unnecessary authz?
- remove unnecessary content-type json headers
- add loggingSystemMock.collect(mockLogger).error assertion
- reconstrcut new MockRouter on beforeEach for better sandboxing
- fix incorrect describe()s -should be it()
- pull out reusable mockDependencies helper (renamed/extended from mockConfig) for tests that don't particularly use config/log but still want to pass type definitions
- Fix comment copy

Co-authored-by: Jason Stoltzfus <jastoltz24@gmail.com>
Co-authored-by: Chris Cressman <chris@chriscressman.com>
Co-authored-by: scottybollinger <scotty.bollinger@elastic.co>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
---
 .eslintrc.js                                  |  12 +
 .github/CODEOWNERS                            |   5 +
 .../collapsible_nav.test.tsx.snap             |   6 +-
 src/core/public/public.api.md                 |   6 +
 src/core/server/server.api.md                 |   6 +
 src/core/utils/default_app_categories.ts      |  12 +-
 x-pack/.i18nrc.json                           |   1 +
 x-pack/plugins/enterprise_search/README.md    |  25 ++
 .../enterprise_search/common/constants.ts     |   7 +
 x-pack/plugins/enterprise_search/kibana.json  |  10 +
 .../public/applications/__mocks__/index.ts    |  13 +
 .../__mocks__/kibana_context.mock.ts          |  17 ++
 .../__mocks__/license_context.mock.ts         |  11 +
 .../__mocks__/mount_with_context.mock.tsx     |  49 ++++
 .../__mocks__/react_router_history.mock.ts    |  25 ++
 .../__mocks__/shallow_usecontext.mock.ts      |  40 ++++
 .../__mocks__/shallow_with_i18n.mock.tsx      |  30 +++
 .../applications/app_search/assets/engine.svg |   3 +
 .../app_search/assets/getting_started.png     | Bin 0 -> 92044 bytes
 .../applications/app_search/assets/logo.svg   |   4 +
 .../app_search/assets/meta_engine.svg         |   4 +
 .../components/empty_states/empty_state.tsx   |  74 ++++++
 .../components/empty_states/empty_states.scss |  19 ++
 .../empty_states/empty_states.test.tsx        |  53 ++++
 .../components/empty_states/error_state.tsx   |  95 ++++++++
 .../components/empty_states/index.ts          |   9 +
 .../components/empty_states/loading_state.tsx |  30 +++
 .../engine_overview/engine_overview.scss      |  27 +++
 .../engine_overview/engine_overview.test.tsx  | 171 +++++++++++++
 .../engine_overview/engine_overview.tsx       | 155 ++++++++++++
 .../engine_overview/engine_table.test.tsx     |  80 +++++++
 .../engine_overview/engine_table.tsx          | 153 ++++++++++++
 .../components/engine_overview/index.ts       |   7 +
 .../engine_overview_header.test.tsx           |  41 ++++
 .../engine_overview_header.tsx                |  72 ++++++
 .../engine_overview_header/index.ts           |   7 +
 .../components/setup_guide/index.ts           |   7 +
 .../setup_guide/setup_guide.test.tsx          |  21 ++
 .../components/setup_guide/setup_guide.tsx    |  64 +++++
 .../applications/app_search/index.test.tsx    |  46 ++++
 .../public/applications/app_search/index.tsx  |  28 +++
 .../public/applications/index.test.tsx        |  40 ++++
 .../public/applications/index.tsx             |  56 +++++
 .../get_enterprise_search_url.test.ts         |  30 +++
 .../get_enterprise_search_url.ts              |  27 +++
 .../shared/enterprise_search_url/index.ts     |   7 +
 .../generate_breadcrumbs.test.ts              | 206 ++++++++++++++++
 .../generate_breadcrumbs.ts                   |  54 +++++
 .../shared/kibana_breadcrumbs/index.ts        |   9 +
 .../set_breadcrumbs.test.tsx                  |  63 +++++
 .../kibana_breadcrumbs/set_breadcrumbs.tsx    |  43 ++++
 .../applications/shared/licensing/index.ts    |   8 +
 .../shared/licensing/license_checks.test.ts   |  33 +++
 .../shared/licensing/license_checks.ts        |  11 +
 .../shared/licensing/license_context.test.tsx |  24 ++
 .../shared/licensing/license_context.tsx      |  29 +++
 .../react_router_helpers/eui_link.test.tsx    |  77 ++++++
 .../shared/react_router_helpers/eui_link.tsx  |  57 +++++
 .../shared/react_router_helpers/index.ts      |   9 +
 .../react_router_helpers/link_events.test.ts  | 102 ++++++++
 .../react_router_helpers/link_events.ts       |  31 +++
 .../applications/shared/setup_guide/index.ts  |   7 +
 .../shared/setup_guide/setup_guide.scss       |  51 ++++
 .../shared/setup_guide/setup_guide.test.tsx   |  44 ++++
 .../shared/setup_guide/setup_guide.tsx        | 226 ++++++++++++++++++
 .../applications/shared/telemetry/index.ts    |   8 +
 .../shared/telemetry/send_telemetry.test.tsx  |  56 +++++
 .../shared/telemetry/send_telemetry.tsx       |  50 ++++
 .../plugins/enterprise_search/public/index.ts |  12 +
 .../enterprise_search/public/plugin.ts        |  88 +++++++
 .../collectors/app_search/telemetry.test.ts   | 143 +++++++++++
 .../server/collectors/app_search/telemetry.ts | 156 ++++++++++++
 .../plugins/enterprise_search/server/index.ts |  29 +++
 .../server/lib/check_access.test.ts           | 128 ++++++++++
 .../server/lib/check_access.ts                |  76 ++++++
 .../lib/enterprise_search_config_api.test.ts  | 111 +++++++++
 .../lib/enterprise_search_config_api.ts       |  78 ++++++
 .../enterprise_search/server/plugin.ts        | 121 ++++++++++
 .../server/routes/__mocks__/index.ts          |   8 +
 .../server/routes/__mocks__/router.mock.ts    | 102 ++++++++
 .../__mocks__/routerDependencies.mock.ts      |  27 +++
 .../server/routes/app_search/engines.test.ts  | 160 +++++++++++++
 .../server/routes/app_search/engines.ts       |  59 +++++
 .../routes/app_search/telemetry.test.ts       | 108 +++++++++
 .../server/routes/app_search/telemetry.ts     |  50 ++++
 .../enterprise_search/public_url.test.ts      |  52 ++++
 .../routes/enterprise_search/public_url.ts    |  26 ++
 .../saved_objects/app_search/telemetry.ts     |  19 ++
 .../privileges/privileges.test.ts             |  14 +-
 .../authorization/privileges/privileges.ts    |   1 +
 .../schema/xpack_plugins.json                 |  34 +++
 x-pack/scripts/functional_tests.js            |   1 +
 .../apis/features/features/features.ts        |   1 +
 .../functional_enterprise_search/README.md    |  41 ++++
 .../app_search/engines.ts                     |  75 ++++++
 .../with_host_configured/index.ts             |  13 +
 .../app_search/setup_guide.ts                 |  36 +++
 .../without_host_configured/index.ts          |  15 ++
 .../base_config.ts                            |  20 ++
 .../ftr_provider_context.d.ts                 |  12 +
 .../page_objects/app_search.ts                |  30 +++
 .../page_objects/index.ts                     |  13 +
 .../services/app_search_client.ts             | 121 ++++++++++
 .../services/app_search_service.ts            |  77 ++++++
 .../services/index.ts                         |  13 +
 .../with_host_configured.config.ts            |  31 +++
 .../without_host_configured.config.ts         |  23 ++
 .../common/nav_links_builder.ts               |   4 +
 .../security_and_spaces/tests/catalogue.ts    |  16 +-
 .../security_and_spaces/tests/nav_links.ts    |  12 +-
 .../security_only/tests/catalogue.ts          |  16 +-
 .../security_only/tests/nav_links.ts          |  10 +-
 112 files changed, 4968 insertions(+), 17 deletions(-)
 create mode 100644 x-pack/plugins/enterprise_search/README.md
 create mode 100644 x-pack/plugins/enterprise_search/common/constants.ts
 create mode 100644 x-pack/plugins/enterprise_search/kibana.json
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/assets/getting_started.png
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.scss
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/loading_state.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/index.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/index.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.scss
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/telemetry/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx
 create mode 100644 x-pack/plugins/enterprise_search/public/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/public/plugin.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/lib/check_access.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/lib/check_access.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/plugin.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/__mocks__/index.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/__mocks__/router.mock.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/__mocks__/routerDependencies.mock.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.test.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.ts
 create mode 100644 x-pack/plugins/enterprise_search/server/saved_objects/app_search/telemetry.ts
 create mode 100644 x-pack/test/functional_enterprise_search/README.md
 create mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/app_search/engines.ts
 create mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/index.ts
 create mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts
 create mode 100644 x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts
 create mode 100644 x-pack/test/functional_enterprise_search/base_config.ts
 create mode 100644 x-pack/test/functional_enterprise_search/ftr_provider_context.d.ts
 create mode 100644 x-pack/test/functional_enterprise_search/page_objects/app_search.ts
 create mode 100644 x-pack/test/functional_enterprise_search/page_objects/index.ts
 create mode 100644 x-pack/test/functional_enterprise_search/services/app_search_client.ts
 create mode 100644 x-pack/test/functional_enterprise_search/services/app_search_service.ts
 create mode 100644 x-pack/test/functional_enterprise_search/services/index.ts
 create mode 100644 x-pack/test/functional_enterprise_search/with_host_configured.config.ts
 create mode 100644 x-pack/test/functional_enterprise_search/without_host_configured.config.ts

diff --git a/.eslintrc.js b/.eslintrc.js
index 8d979dc0f8645..4425ad3a12659 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -906,6 +906,18 @@ module.exports = {
       },
     },
 
+    /**
+     * Enterprise Search overrides
+     */
+    {
+      files: ['x-pack/plugins/enterprise_search/**/*.{ts,tsx}'],
+      excludedFiles: ['x-pack/plugins/enterprise_search/**/*.{test,mock}.{ts,tsx}'],
+      rules: {
+        'react-hooks/exhaustive-deps': 'off',
+        '@typescript-eslint/no-explicit-any': 'error',
+      },
+    },
+
     /**
      * disable jsx-a11y for kbn-ui-framework
      */
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4aab9943022d4..f053c6da9c29b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -201,6 +201,11 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib
 # Design
 **/*.scss  @elastic/kibana-design
 
+# Enterprise Search
+/x-pack/plugins/enterprise_search/ @elastic/app-search-frontend @elastic/workplace-search-frontend
+/x-pack/test/functional_enterprise_search/ @elastic/app-search-frontend @elastic/workplace-search-frontend
+/x-pack/plugins/enterprise_search/**/*.scss @elastic/ent-search-design
+
 # Elasticsearch UI
 /src/plugins/dev_tools/ @elastic/es-ui
 /src/plugins/console/  @elastic/es-ui
diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
index 9fee7b50f371b..1cfded4dc7b8f 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
@@ -149,7 +149,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
             "euiIconType": "logoSecurity",
             "id": "security",
             "label": "Security",
-            "order": 3000,
+            "order": 4000,
           },
           "data-test-subj": "siem",
           "href": "siem",
@@ -164,7 +164,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
             "euiIconType": "logoObservability",
             "id": "observability",
             "label": "Observability",
-            "order": 2000,
+            "order": 3000,
           },
           "data-test-subj": "metrics",
           "href": "metrics",
@@ -233,7 +233,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
             "euiIconType": "logoObservability",
             "id": "observability",
             "label": "Observability",
-            "order": 2000,
+            "order": 3000,
           },
           "data-test-subj": "logs",
           "href": "logs",
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 86e281a49b744..40fc3f977006f 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -582,6 +582,12 @@ export const DEFAULT_APP_CATEGORIES: Readonly<{
         euiIconType: string;
         order: number;
     };
+    enterpriseSearch: {
+        id: string;
+        label: string;
+        order: number;
+        euiIconType: string;
+    };
     observability: {
         id: string;
         label: string;
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index efeafc9e68d35..95912c3af63e5 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -566,6 +566,12 @@ export const DEFAULT_APP_CATEGORIES: Readonly<{
         euiIconType: string;
         order: number;
     };
+    enterpriseSearch: {
+        id: string;
+        label: string;
+        order: number;
+        euiIconType: string;
+    };
     observability: {
         id: string;
         label: string;
diff --git a/src/core/utils/default_app_categories.ts b/src/core/utils/default_app_categories.ts
index 5708bcfeac31a..cc9bfb1db04d5 100644
--- a/src/core/utils/default_app_categories.ts
+++ b/src/core/utils/default_app_categories.ts
@@ -29,20 +29,28 @@ export const DEFAULT_APP_CATEGORIES = Object.freeze({
     euiIconType: 'logoKibana',
     order: 1000,
   },
+  enterpriseSearch: {
+    id: 'enterpriseSearch',
+    label: i18n.translate('core.ui.enterpriseSearchNavList.label', {
+      defaultMessage: 'Enterprise Search',
+    }),
+    order: 2000,
+    euiIconType: 'logoEnterpriseSearch',
+  },
   observability: {
     id: 'observability',
     label: i18n.translate('core.ui.observabilityNavList.label', {
       defaultMessage: 'Observability',
     }),
     euiIconType: 'logoObservability',
-    order: 2000,
+    order: 3000,
   },
   security: {
     id: 'security',
     label: i18n.translate('core.ui.securityNavList.label', {
       defaultMessage: 'Security',
     }),
-    order: 3000,
+    order: 4000,
     euiIconType: 'logoSecurity',
   },
   management: {
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index 596ba17d343c0..d0055008eb9bf 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -16,6 +16,7 @@
     "xpack.data": "plugins/data_enhanced",
     "xpack.embeddableEnhanced": "plugins/embeddable_enhanced",
     "xpack.endpoint": "plugins/endpoint",
+    "xpack.enterpriseSearch": "plugins/enterprise_search",
     "xpack.features": "plugins/features",
     "xpack.fileUpload": "plugins/file_upload",
     "xpack.globalSearch": ["plugins/global_search"],
diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md
new file mode 100644
index 0000000000000..8c316c848184b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/README.md
@@ -0,0 +1,25 @@
+# Enterprise Search
+
+## Overview
+
+This plugin's goal is to provide a Kibana user interface to the Enterprise Search solution's products (App Search and Workplace Search). In its current MVP state, the plugin provides a basic engines overview from App Search with the goal of gathering user feedback and raising product awareness.
+
+## Development
+
+1. When developing locally, Enterprise Search should be running locally alongside Kibana on `localhost:3002`.
+2. Update `config/kibana.dev.yml` with `enterpriseSearch.host: 'http://localhost:3002'`
+3. For faster QA/development, run Enterprise Search on [elasticsearch-native auth](https://www.elastic.co/guide/en/app-search/current/security-and-users.html#app-search-self-managed-security-and-user-management-elasticsearch-native-realm) and log in as the `elastic` superuser on Kibana.
+
+## Testing
+
+### Unit tests
+
+From `kibana-root-folder/x-pack`, run:
+
+```bash
+yarn test:jest plugins/enterprise_search
+```
+
+### E2E tests
+
+See [our functional test runner README](../../test/functional_enterprise_search).
diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts
new file mode 100644
index 0000000000000..c134131caba75
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/common/constants.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const ENGINES_PAGE_SIZE = 10;
diff --git a/x-pack/plugins/enterprise_search/kibana.json b/x-pack/plugins/enterprise_search/kibana.json
new file mode 100644
index 0000000000000..9a2daefcd8c6e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/kibana.json
@@ -0,0 +1,10 @@
+{
+  "id": "enterpriseSearch",
+  "version": "kibana",
+  "kibanaVersion": "kibana",
+  "requiredPlugins": ["home", "features", "licensing"],
+  "configPath": ["enterpriseSearch"],
+  "optionalPlugins": ["usageCollection", "security"],
+  "server": true,
+  "ui": true
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
new file mode 100644
index 0000000000000..14fde357a980a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { mockHistory } from './react_router_history.mock';
+export { mockKibanaContext } from './kibana_context.mock';
+export { mockLicenseContext } from './license_context.mock';
+export { mountWithContext, mountWithKibanaContext } from './mount_with_context.mock';
+export { shallowWithIntl } from './shallow_with_i18n.mock';
+
+// Note: shallow_usecontext must be imported directly as a file
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts
new file mode 100644
index 0000000000000..fcfa1b0a21f13
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { httpServiceMock } from 'src/core/public/mocks';
+
+/**
+ * A set of default Kibana context values to use across component tests.
+ * @see enterprise_search/public/index.tsx for the KibanaContext definition/import
+ */
+export const mockKibanaContext = {
+  http: httpServiceMock.createSetupContract(),
+  setBreadcrumbs: jest.fn(),
+  enterpriseSearchUrl: 'http://localhost:3002',
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts
new file mode 100644
index 0000000000000..7c37ecc7cde1b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { licensingMock } from '../../../../licensing/public/mocks';
+
+export const mockLicenseContext = {
+  license: licensingMock.createLicense(),
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
new file mode 100644
index 0000000000000..dfcda544459d4
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+
+import { I18nProvider } from '@kbn/i18n/react';
+import { KibanaContext } from '../';
+import { mockKibanaContext } from './kibana_context.mock';
+import { LicenseContext } from '../shared/licensing';
+import { mockLicenseContext } from './license_context.mock';
+
+/**
+ * This helper mounts a component with all the contexts/providers used
+ * by the production app, while allowing custom context to be
+ * passed in via a second arg
+ *
+ * Example usage:
+ *
+ * const wrapper = mountWithContext(<Component />, { enterpriseSearchUrl: 'someOverride', license: {} });
+ */
+export const mountWithContext = (children: React.ReactNode, context?: object) => {
+  return mount(
+    <I18nProvider>
+      <KibanaContext.Provider value={{ ...mockKibanaContext, ...context }}>
+        <LicenseContext.Provider value={{ ...mockLicenseContext, ...context }}>
+          {children}
+        </LicenseContext.Provider>
+      </KibanaContext.Provider>
+    </I18nProvider>
+  );
+};
+
+/**
+ * This helper mounts a component with just the default KibanaContext -
+ * useful for isolated / helper components that only need this context
+ *
+ * Same usage/override functionality as mountWithContext
+ */
+export const mountWithKibanaContext = (children: React.ReactNode, context?: object) => {
+  return mount(
+    <KibanaContext.Provider value={{ ...mockKibanaContext, ...context }}>
+      {children}
+    </KibanaContext.Provider>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts
new file mode 100644
index 0000000000000..fd422465d87f1
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * NOTE: This variable name MUST start with 'mock*' in order for
+ * Jest to accept its use within a jest.mock()
+ */
+export const mockHistory = {
+  createHref: jest.fn(({ pathname }) => `/enterprise_search${pathname}`),
+  push: jest.fn(),
+  location: {
+    pathname: '/current-path',
+  },
+};
+
+jest.mock('react-router-dom', () => ({
+  useHistory: jest.fn(() => mockHistory),
+}));
+
+/**
+ * For example usage, @see public/applications/shared/react_router_helpers/eui_link.test.tsx
+ */
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
new file mode 100644
index 0000000000000..767a52a75d1fb
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * NOTE: These variable names MUST start with 'mock*' in order for
+ * Jest to accept its use within a jest.mock()
+ */
+import { mockKibanaContext } from './kibana_context.mock';
+import { mockLicenseContext } from './license_context.mock';
+
+jest.mock('react', () => ({
+  ...(jest.requireActual('react') as object),
+  useContext: jest.fn(() => ({ ...mockKibanaContext, ...mockLicenseContext })),
+}));
+
+/**
+ * Example usage within a component test using shallow():
+ *
+ * import '../../../test_utils/mock_shallow_usecontext'; // Must come before React's import, adjust relative path as needed
+ *
+ * import React from 'react';
+ * import { shallow } from 'enzyme';
+ *
+ * // ... etc.
+ */
+
+/**
+ * If you need to override the default mock context values, you can do so via jest.mockImplementation:
+ *
+ * import React, { useContext } from 'react';
+ *
+ * // ... etc.
+ *
+ * it('some test', () => {
+ *   useContext.mockImplementationOnce(() => ({ enterpriseSearchUrl: 'someOverride' }));
+ * });
+ */
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx
new file mode 100644
index 0000000000000..ae7d0b09f9872
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { I18nProvider } from '@kbn/i18n/react';
+import { IntlProvider } from 'react-intl';
+
+const intlProvider = new IntlProvider({ locale: 'en', messages: {} }, {});
+const { intl } = intlProvider.getChildContext();
+
+/**
+ * This helper shallow wraps a component with react-intl's <I18nProvider> which
+ * fixes "Could not find required `intl` object" console errors when running tests
+ *
+ * Example usage (should be the same as shallow()):
+ *
+ * const wrapper = shallowWithIntl(<Component />);
+ */
+export const shallowWithIntl = (children: React.ReactNode) => {
+  const context = { context: { intl } };
+
+  return shallow(<I18nProvider>{children}</I18nProvider>, context)
+    .childAt(0)
+    .dive(context)
+    .shallow();
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg
new file mode 100644
index 0000000000000..ceab918e92e70
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/engine.svg
@@ -0,0 +1,3 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="18" height="18">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M9.776 1.389a7.66 7.66 0 00-.725-.04v.001H9a7.65 7.65 0 00-.051 15.301v-.001H9a7.65 7.65 0 00.776-15.261zm-1.52 1.254a6.401 6.401 0 00.02 12.716l2.333-3.791a.875.875 0 00-.354-1.242l-3.07-1.534a2.125 2.125 0 01-.859-3.015l1.93-3.134zm1.489 12.714l1.929-3.134a2.125 2.125 0 00-.86-3.015l-3.07-1.534a.875.875 0 01-.353-1.242L9.724 2.64a6.401 6.401 0 01.02 12.717z" fill="#000"/>
+</svg>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/getting_started.png b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/getting_started.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d988d14f0483c31045783b714eedde9baac92b9
GIT binary patch
literal 92044
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
b{wDtaU9#Ss{&OGS00000NkvXXu0mjf=D5g<

literal 0
HcmV?d00001

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg
new file mode 100644
index 0000000000000..2284a425b5add
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/logo.svg
@@ -0,0 +1,4 @@
+<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="none">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 11.915L27 5.277 19.5.937a7.002 7.002 0 00-7 0l-8 4.62A7 7 0 001 11.62v9.237a7 7 0 003.5 6.062l7.5 4.33V17.979a7 7 0 013.5-6.063zM10 27.785v-9.808a9 9 0 014.5-7.793l8.503-4.91L18.5 2.67a5.003 5.003 0 00-5 0l-8 4.619a5 5 0 00-2.5 4.33v9.238a5 5 0 002.5 4.33l4.5 2.598z" fill="#343741" />
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M18.409 13.55a7.09 7.09 0 011.035 1.711A6.93 6.93 0 0120 17.978v13.27l7.5-4.33a7 7 0 003.5-6.061v-9.238a6.992 6.992 0 00-1.587-4.422L18.409 13.55zm2.777.705A8.933 8.933 0 0122 17.978v9.807l4.5-2.598a5 5 0 002.5-4.33v-9.238c0-.588-.106-1.161-.303-1.7l-7.51 4.336z" fill="#017D73" />
+</svg>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg
new file mode 100644
index 0000000000000..4e01e9a0b34fb
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/assets/meta_engine.svg
@@ -0,0 +1,4 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="18" height="18">
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M4 9a5.002 5.002 0 005 5 5 5 0 10-5-5zm5.506 1.653L8.37 12.697a3.751 3.751 0 01-.003-7.394L7.402 7.04a1.625 1.625 0 00.519 2.142l1.465.976a.375.375 0 01.12.495zm1.092.607l-.777 1.4a3.751 3.751 0 00-.04-7.329L8.494 7.647a.375.375 0 00.12.495l1.465.976c.705.47.93 1.402.52 2.142z" fill="#000"/>
+  <path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 1.375A5.125 5.125 0 001.375 6.5v5A5.125 5.125 0 006.5 16.625h5a5.125 5.125 0 005.125-5.125v-5A5.125 5.125 0 0011.5 1.375h-5zM2.625 6.5A3.875 3.875 0 016.5 2.625h5A3.875 3.875 0 0115.375 6.5v5a3.875 3.875 0 01-3.875 3.875h-5A3.875 3.875 0 012.625 11.5v-5z" fill="#000"/>
+  </svg>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
new file mode 100644
index 0000000000000..9bb5cd3bffdf5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
@@ -0,0 +1,74 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import { EuiPage, EuiPageBody, EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { sendTelemetry } from '../../../shared/telemetry';
+import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs';
+import { KibanaContext, IKibanaContext } from '../../../index';
+
+import { EngineOverviewHeader } from '../engine_overview_header';
+
+import './empty_states.scss';
+
+export const EmptyState: React.FC = () => {
+  const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+
+  const buttonProps = {
+    href: `${enterpriseSearchUrl}/as/engines/new`,
+    target: '_blank',
+    onClick: () =>
+      sendTelemetry({
+        http,
+        product: 'app_search',
+        action: 'clicked',
+        metric: 'create_first_engine_button',
+      }),
+  };
+
+  return (
+    <EuiPage restrictWidth>
+      <SetBreadcrumbs isRoot />
+
+      <EuiPageBody>
+        <EngineOverviewHeader />
+        <EuiPageContent className="emptyState">
+          <EuiEmptyPrompt
+            className="emptyState__prompt"
+            iconType="eyeClosed"
+            title={
+              <h2>
+                <FormattedMessage
+                  id="xpack.enterpriseSearch.appSearch.emptyState.title"
+                  defaultMessage="Create your first engine"
+                />
+              </h2>
+            }
+            titleSize="l"
+            body={
+              <p>
+                <FormattedMessage
+                  id="xpack.enterpriseSearch.appSearch.emptyState.description1"
+                  defaultMessage="An App Search engine stores the documents for your search experience."
+                />
+              </p>
+            }
+            actions={
+              <EuiButton iconType="popout" fill {...buttonProps}>
+                <FormattedMessage
+                  id="xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta"
+                  defaultMessage="Create an engine"
+                />
+              </EuiButton>
+            }
+          />
+        </EuiPageContent>
+      </EuiPageBody>
+    </EuiPage>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.scss
new file mode 100644
index 0000000000000..01b0903add559
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.scss
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * Empty/Error UI states
+ */
+.emptyState {
+  min-height: $euiSizeXXL * 11.25;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+
+  &__prompt > .euiIcon {
+    margin-bottom: $euiSizeS;
+  }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.test.tsx
new file mode 100644
index 0000000000000..12bf003564103
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_states.test.tsx
@@ -0,0 +1,53 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import '../../../__mocks__/shallow_usecontext.mock';
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { EuiEmptyPrompt, EuiButton, EuiLoadingContent } from '@elastic/eui';
+
+jest.mock('../../../shared/telemetry', () => ({
+  sendTelemetry: jest.fn(),
+  SendAppSearchTelemetry: jest.fn(),
+}));
+import { sendTelemetry } from '../../../shared/telemetry';
+
+import { ErrorState, EmptyState, LoadingState } from './';
+
+describe('ErrorState', () => {
+  it('renders', () => {
+    const wrapper = shallow(<ErrorState />);
+
+    expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
+  });
+});
+
+describe('EmptyState', () => {
+  it('renders', () => {
+    const wrapper = shallow(<EmptyState />);
+
+    expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
+  });
+
+  it('sends telemetry on create first engine click', () => {
+    const wrapper = shallow(<EmptyState />);
+    const prompt = wrapper.find(EuiEmptyPrompt).dive();
+    const button = prompt.find(EuiButton);
+
+    button.simulate('click');
+    expect(sendTelemetry).toHaveBeenCalled();
+    (sendTelemetry as jest.Mock).mockClear();
+  });
+});
+
+describe('LoadingState', () => {
+  it('renders', () => {
+    const wrapper = shallow(<LoadingState />);
+
+    expect(wrapper.find(EuiLoadingContent)).toHaveLength(2);
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx
new file mode 100644
index 0000000000000..d8eeff2aba1c6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/error_state.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import { EuiPage, EuiPageBody, EuiPageContent, EuiEmptyPrompt, EuiCode } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { EuiButton } from '../../../shared/react_router_helpers';
+import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs';
+import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
+import { KibanaContext, IKibanaContext } from '../../../index';
+import { EngineOverviewHeader } from '../engine_overview_header';
+
+import './empty_states.scss';
+
+export const ErrorState: React.FC = () => {
+  const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
+
+  return (
+    <EuiPage restrictWidth>
+      <SetBreadcrumbs isRoot />
+      <SendTelemetry action="error" metric="cannot_connect" />
+
+      <EuiPageBody>
+        <EngineOverviewHeader isButtonDisabled />
+        <EuiPageContent className="emptyState">
+          <EuiEmptyPrompt
+            className="emptyState__prompt"
+            iconType="alert"
+            iconColor="danger"
+            title={
+              <h2>
+                <FormattedMessage
+                  id="xpack.enterpriseSearch.appSearch.errorConnectingState.title"
+                  defaultMessage="Unable to connect"
+                />
+              </h2>
+            }
+            titleSize="l"
+            body={
+              <>
+                <p>
+                  <FormattedMessage
+                    id="xpack.enterpriseSearch.appSearch.errorConnectingState.description1"
+                    defaultMessage="We can’t establish a connection to App Search at the host URL: {enterpriseSearchUrl}"
+                    values={{
+                      enterpriseSearchUrl: <EuiCode>{enterpriseSearchUrl}</EuiCode>,
+                    }}
+                  />
+                </p>
+                <ol className="eui-textLeft">
+                  <li>
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.appSearch.errorConnectingState.description2"
+                      defaultMessage="Ensure the host URL is configured correctly in {configFile}."
+                      values={{
+                        configFile: <EuiCode>config/kibana.yml</EuiCode>,
+                      }}
+                    />
+                  </li>
+                  <li>
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.appSearch.errorConnectingState.description3"
+                      defaultMessage="Confirm that the App Search server is responsive."
+                    />
+                  </li>
+                  <li>
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.appSearch.errorConnectingState.description4"
+                      defaultMessage="Review the Setup guide or check your server log for {pluginLog} log messages."
+                      values={{
+                        pluginLog: <EuiCode>[enterpriseSearch][plugins]</EuiCode>,
+                      }}
+                    />
+                  </li>
+                </ol>
+              </>
+            }
+            actions={
+              <EuiButton iconType="help" fill to="/setup_guide">
+                <FormattedMessage
+                  id="xpack.enterpriseSearch.appSearch.errorConnectingState.setupGuideCta"
+                  defaultMessage="Review setup guide"
+                />
+              </EuiButton>
+            }
+          />
+        </EuiPageContent>
+      </EuiPageBody>
+    </EuiPage>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/index.ts
new file mode 100644
index 0000000000000..e92bf214c4cc7
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { LoadingState } from './loading_state';
+export { EmptyState } from './empty_state';
+export { ErrorState } from './error_state';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/loading_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/loading_state.tsx
new file mode 100644
index 0000000000000..2be917c8df096
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/loading_state.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLoadingContent } from '@elastic/eui';
+
+import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs';
+import { EngineOverviewHeader } from '../engine_overview_header';
+
+import './empty_states.scss';
+
+export const LoadingState: React.FC = () => {
+  return (
+    <EuiPage restrictWidth>
+      <SetBreadcrumbs isRoot />
+
+      <EuiPageBody>
+        <EngineOverviewHeader />
+        <EuiPageContent className="emptyState">
+          <EuiLoadingContent lines={5} />
+          <EuiSpacer size="xxl" />
+          <EuiLoadingContent lines={4} />
+        </EuiPageContent>
+      </EuiPageBody>
+    </EuiPage>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss
new file mode 100644
index 0000000000000..2c7f7de6458e2
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.scss
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * Engine Overview
+ */
+.engineOverview {
+  width: 100%;
+
+  &__body {
+    padding: $euiSize;
+
+    @include euiBreakpoint('m', 'l', 'xl') {
+      padding: $euiSizeXL;
+    }
+  }
+}
+
+.engineIcon {
+  display: inline-block;
+  width: $euiSize;
+  height: $euiSize;
+  margin-right: $euiSizeXS;
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
new file mode 100644
index 0000000000000..4d2a2ea1df9aa
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
@@ -0,0 +1,171 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import '../../../__mocks__/react_router_history.mock';
+
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { render, ReactWrapper } from 'enzyme';
+
+import { I18nProvider } from '@kbn/i18n/react';
+import { KibanaContext } from '../../../';
+import { LicenseContext } from '../../../shared/licensing';
+import { mountWithContext, mockKibanaContext } from '../../../__mocks__';
+
+import { EmptyState, ErrorState } from '../empty_states';
+import { EngineTable, IEngineTablePagination } from './engine_table';
+
+import { EngineOverview } from './';
+
+describe('EngineOverview', () => {
+  describe('non-happy-path states', () => {
+    it('isLoading', () => {
+      // We use render() instead of mount() here to not trigger lifecycle methods (i.e., useEffect)
+      // TODO: Consider pulling this out to a renderWithContext mock/helper
+      const wrapper: Cheerio = render(
+        <I18nProvider>
+          <KibanaContext.Provider value={{ http: {} }}>
+            <LicenseContext.Provider value={{ license: {} }}>
+              <EngineOverview />
+            </LicenseContext.Provider>
+          </KibanaContext.Provider>
+        </I18nProvider>
+      );
+
+      // render() directly renders HTML which means we have to look for selectors instead of for LoadingState directly
+      expect(wrapper.find('.euiLoadingContent')).toHaveLength(2);
+    });
+
+    it('isEmpty', async () => {
+      const wrapper = await mountWithApiMock({
+        get: () => ({
+          results: [],
+          meta: { page: { total_results: 0 } },
+        }),
+      });
+
+      expect(wrapper.find(EmptyState)).toHaveLength(1);
+    });
+
+    it('hasErrorConnecting', async () => {
+      const wrapper = await mountWithApiMock({
+        get: () => ({ invalidPayload: true }),
+      });
+      expect(wrapper.find(ErrorState)).toHaveLength(1);
+    });
+  });
+
+  describe('happy-path states', () => {
+    const mockedApiResponse = {
+      results: [
+        {
+          name: 'hello-world',
+          created_at: 'Fri, 1 Jan 1970 12:00:00 +0000',
+          document_count: 50,
+          field_count: 10,
+        },
+      ],
+      meta: {
+        page: {
+          current: 1,
+          total_pages: 10,
+          total_results: 100,
+          size: 10,
+        },
+      },
+    };
+    const mockApi = jest.fn(() => mockedApiResponse);
+    let wrapper: ReactWrapper;
+
+    beforeAll(async () => {
+      wrapper = await mountWithApiMock({ get: mockApi });
+    });
+
+    it('renders', () => {
+      expect(wrapper.find(EngineTable)).toHaveLength(1);
+    });
+
+    it('calls the engines API', () => {
+      expect(mockApi).toHaveBeenNthCalledWith(1, '/api/app_search/engines', {
+        query: {
+          type: 'indexed',
+          pageIndex: 1,
+        },
+      });
+    });
+
+    describe('pagination', () => {
+      const getTablePagination: () => IEngineTablePagination = () =>
+        wrapper.find(EngineTable).first().prop('pagination');
+
+      it('passes down page data from the API', () => {
+        const pagination = getTablePagination();
+
+        expect(pagination.totalEngines).toEqual(100);
+        expect(pagination.pageIndex).toEqual(0);
+      });
+
+      it('re-polls the API on page change', async () => {
+        await act(async () => getTablePagination().onPaginate(5));
+        wrapper.update();
+
+        expect(mockApi).toHaveBeenLastCalledWith('/api/app_search/engines', {
+          query: {
+            type: 'indexed',
+            pageIndex: 5,
+          },
+        });
+        expect(getTablePagination().pageIndex).toEqual(4);
+      });
+    });
+
+    describe('when on a platinum license', () => {
+      beforeAll(async () => {
+        mockApi.mockClear();
+        wrapper = await mountWithApiMock({
+          license: { type: 'platinum', isActive: true },
+          get: mockApi,
+        });
+      });
+
+      it('renders a 2nd meta engines table', () => {
+        expect(wrapper.find(EngineTable)).toHaveLength(2);
+      });
+
+      it('makes a 2nd call to the engines API with type meta', () => {
+        expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', {
+          query: {
+            type: 'meta',
+            pageIndex: 1,
+          },
+        });
+      });
+    });
+  });
+
+  /**
+   * Test helpers
+   */
+
+  const mountWithApiMock = async ({ get, license }: { get(): any; license?: object }) => {
+    let wrapper: ReactWrapper | undefined;
+    const httpMock = { ...mockKibanaContext.http, get };
+
+    // We get a lot of act() warning/errors in the terminal without this.
+    // TBH, I don't fully understand why since Enzyme's mount is supposed to
+    // have act() baked in - could be because of the wrapping context provider?
+    await act(async () => {
+      wrapper = mountWithContext(<EngineOverview />, { http: httpMock, license });
+    });
+    if (wrapper) {
+      wrapper.update(); // This seems to be required for the DOM to actually update
+
+      return wrapper;
+    } else {
+      throw new Error('Could not mount wrapper');
+    }
+  };
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
new file mode 100644
index 0000000000000..13d092a657d11
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
@@ -0,0 +1,155 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext, useEffect, useState } from 'react';
+import {
+  EuiPage,
+  EuiPageBody,
+  EuiPageContent,
+  EuiPageContentHeader,
+  EuiPageContentBody,
+  EuiTitle,
+  EuiSpacer,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs';
+import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
+import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing';
+import { KibanaContext, IKibanaContext } from '../../../index';
+
+import EnginesIcon from '../../assets/engine.svg';
+import MetaEnginesIcon from '../../assets/meta_engine.svg';
+
+import { LoadingState, EmptyState, ErrorState } from '../empty_states';
+import { EngineOverviewHeader } from '../engine_overview_header';
+import { EngineTable } from './engine_table';
+
+import './engine_overview.scss';
+
+interface IGetEnginesParams {
+  type: string;
+  pageIndex: number;
+}
+interface ISetEnginesCallbacks {
+  setResults: React.Dispatch<React.SetStateAction<never[]>>;
+  setResultsTotal: React.Dispatch<React.SetStateAction<number>>;
+}
+
+export const EngineOverview: React.FC = () => {
+  const { http } = useContext(KibanaContext) as IKibanaContext;
+  const { license } = useContext(LicenseContext) as ILicenseContext;
+
+  const [isLoading, setIsLoading] = useState(true);
+  const [hasErrorConnecting, setHasErrorConnecting] = useState(false);
+
+  const [engines, setEngines] = useState([]);
+  const [enginesPage, setEnginesPage] = useState(1);
+  const [enginesTotal, setEnginesTotal] = useState(0);
+  const [metaEngines, setMetaEngines] = useState([]);
+  const [metaEnginesPage, setMetaEnginesPage] = useState(1);
+  const [metaEnginesTotal, setMetaEnginesTotal] = useState(0);
+
+  const getEnginesData = async ({ type, pageIndex }: IGetEnginesParams) => {
+    return await http.get('/api/app_search/engines', {
+      query: { type, pageIndex },
+    });
+  };
+  const setEnginesData = async (params: IGetEnginesParams, callbacks: ISetEnginesCallbacks) => {
+    try {
+      const response = await getEnginesData(params);
+
+      callbacks.setResults(response.results);
+      callbacks.setResultsTotal(response.meta.page.total_results);
+
+      setIsLoading(false);
+    } catch (error) {
+      setHasErrorConnecting(true);
+    }
+  };
+
+  useEffect(() => {
+    const params = { type: 'indexed', pageIndex: enginesPage };
+    const callbacks = { setResults: setEngines, setResultsTotal: setEnginesTotal };
+
+    setEnginesData(params, callbacks);
+  }, [enginesPage]);
+
+  useEffect(() => {
+    if (hasPlatinumLicense(license)) {
+      const params = { type: 'meta', pageIndex: metaEnginesPage };
+      const callbacks = { setResults: setMetaEngines, setResultsTotal: setMetaEnginesTotal };
+
+      setEnginesData(params, callbacks);
+    }
+  }, [license, metaEnginesPage]);
+
+  if (hasErrorConnecting) return <ErrorState />;
+  if (isLoading) return <LoadingState />;
+  if (!engines.length) return <EmptyState />;
+
+  return (
+    <EuiPage restrictWidth className="engineOverview">
+      <SetBreadcrumbs isRoot />
+      <SendTelemetry action="viewed" metric="engines_overview" />
+
+      <EuiPageBody>
+        <EngineOverviewHeader />
+
+        <EuiPageContent panelPaddingSize="s" className="engineOverview__body">
+          <EuiPageContentHeader>
+            <EuiTitle size="s">
+              <h2>
+                <img src={EnginesIcon} alt="" className="engineIcon" />
+                <FormattedMessage
+                  id="xpack.enterpriseSearch.appSearch.enginesOverview.engines"
+                  defaultMessage="Engines"
+                />
+              </h2>
+            </EuiTitle>
+          </EuiPageContentHeader>
+          <EuiPageContentBody data-test-subj="appSearchEngines">
+            <EngineTable
+              data={engines}
+              pagination={{
+                totalEngines: enginesTotal,
+                pageIndex: enginesPage - 1,
+                onPaginate: setEnginesPage,
+              }}
+            />
+          </EuiPageContentBody>
+
+          {metaEngines.length > 0 && (
+            <>
+              <EuiSpacer size="xl" />
+              <EuiPageContentHeader>
+                <EuiTitle size="s">
+                  <h2>
+                    <img src={MetaEnginesIcon} alt="" className="engineIcon" />
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines"
+                      defaultMessage="Meta Engines"
+                    />
+                  </h2>
+                </EuiTitle>
+              </EuiPageContentHeader>
+              <EuiPageContentBody data-test-subj="appSearchMetaEngines">
+                <EngineTable
+                  data={metaEngines}
+                  pagination={{
+                    totalEngines: metaEnginesTotal,
+                    pageIndex: metaEnginesPage - 1,
+                    onPaginate: setMetaEnginesPage,
+                  }}
+                />
+              </EuiPageContentBody>
+            </>
+          )}
+        </EuiPageContent>
+      </EuiPageBody>
+    </EuiPage>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx
new file mode 100644
index 0000000000000..46b6e61e352de
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiLink } from '@elastic/eui';
+
+import { mountWithContext } from '../../../__mocks__';
+jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() }));
+import { sendTelemetry } from '../../../shared/telemetry';
+
+import { EngineTable } from './engine_table';
+
+describe('EngineTable', () => {
+  const onPaginate = jest.fn(); // onPaginate updates the engines API call upstream
+
+  const wrapper = mountWithContext(
+    <EngineTable
+      data={[
+        {
+          name: 'test-engine',
+          created_at: 'Fri, 1 Jan 1970 12:00:00 +0000',
+          document_count: 99999,
+          field_count: 10,
+        },
+      ]}
+      pagination={{
+        totalEngines: 50,
+        pageIndex: 0,
+        onPaginate,
+      }}
+    />
+  );
+  const table = wrapper.find(EuiBasicTable);
+
+  it('renders', () => {
+    expect(table).toHaveLength(1);
+    expect(table.prop('pagination').totalItemCount).toEqual(50);
+
+    const tableContent = table.text();
+    expect(tableContent).toContain('test-engine');
+    expect(tableContent).toContain('January 1, 1970');
+    expect(tableContent).toContain('99,999');
+    expect(tableContent).toContain('10');
+
+    expect(table.find(EuiPagination).find(EuiButtonEmpty)).toHaveLength(5); // Should display 5 pages at 10 engines per page
+  });
+
+  it('contains engine links which send telemetry', () => {
+    const engineLinks = wrapper.find(EuiLink);
+
+    engineLinks.forEach((link) => {
+      expect(link.prop('href')).toEqual('http://localhost:3002/as/engines/test-engine');
+      link.simulate('click');
+
+      expect(sendTelemetry).toHaveBeenCalledWith({
+        http: expect.any(Object),
+        product: 'app_search',
+        action: 'clicked',
+        metric: 'engine_table_link',
+      });
+    });
+  });
+
+  it('triggers onPaginate', () => {
+    table.prop('onChange')({ page: { index: 4 } });
+
+    expect(onPaginate).toHaveBeenCalledWith(5);
+  });
+
+  it('handles empty data', () => {
+    const emptyWrapper = mountWithContext(
+      <EngineTable data={[]} pagination={{ totalEngines: 0, pageIndex: 0, onPaginate: () => {} }} />
+    );
+    const emptyTable = emptyWrapper.find(EuiBasicTable);
+    expect(emptyTable.prop('pagination').pageIndex).toEqual(0);
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
new file mode 100644
index 0000000000000..1e58d820dc83b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
@@ -0,0 +1,153 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import { EuiBasicTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui';
+import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+
+import { sendTelemetry } from '../../../shared/telemetry';
+import { KibanaContext, IKibanaContext } from '../../../index';
+
+import { ENGINES_PAGE_SIZE } from '../../../../../common/constants';
+
+export interface IEngineTableData {
+  name: string;
+  created_at: string;
+  document_count: number;
+  field_count: number;
+}
+export interface IEngineTablePagination {
+  totalEngines: number;
+  pageIndex: number;
+  onPaginate(pageIndex: number): void;
+}
+export interface IEngineTableProps {
+  data: IEngineTableData[];
+  pagination: IEngineTablePagination;
+}
+export interface IOnChange {
+  page: {
+    index: number;
+  };
+}
+
+export const EngineTable: React.FC<IEngineTableProps> = ({
+  data,
+  pagination: { totalEngines, pageIndex, onPaginate },
+}) => {
+  const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+  const engineLinkProps = (name: string) => ({
+    href: `${enterpriseSearchUrl}/as/engines/${name}`,
+    target: '_blank',
+    onClick: () =>
+      sendTelemetry({
+        http,
+        product: 'app_search',
+        action: 'clicked',
+        metric: 'engine_table_link',
+      }),
+  });
+
+  const columns: Array<EuiBasicTableColumn<IEngineTableData>> = [
+    {
+      field: 'name',
+      name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', {
+        defaultMessage: 'Name',
+      }),
+      render: (name: string) => (
+        <EuiLink data-test-subj="engineNameLink" {...engineLinkProps(name)}>
+          {name}
+        </EuiLink>
+      ),
+      width: '30%',
+      truncateText: true,
+      mobileOptions: {
+        header: true,
+        // Note: the below props are valid props per https://elastic.github.io/eui/#/tabular-content/tables (Responsive tables), but EUI's types have a bug reporting it as an error
+        // @ts-ignore
+        enlarge: true,
+        fullWidth: true,
+        truncateText: false,
+      },
+    },
+    {
+      field: 'created_at',
+      name: i18n.translate(
+        'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt',
+        {
+          defaultMessage: 'Created At',
+        }
+      ),
+      dataType: 'string',
+      render: (dateString: string) => (
+        // e.g., January 1, 1970
+        <FormattedDate value={new Date(dateString)} year="numeric" month="long" day="numeric" />
+      ),
+    },
+    {
+      field: 'document_count',
+      name: i18n.translate(
+        'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount',
+        {
+          defaultMessage: 'Document Count',
+        }
+      ),
+      dataType: 'number',
+      render: (number: number) => <FormattedNumber value={number} />,
+      truncateText: true,
+    },
+    {
+      field: 'field_count',
+      name: i18n.translate(
+        'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount',
+        {
+          defaultMessage: 'Field Count',
+        }
+      ),
+      dataType: 'number',
+      render: (number: number) => <FormattedNumber value={number} />,
+      truncateText: true,
+    },
+    {
+      field: 'name',
+      name: i18n.translate(
+        'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions',
+        {
+          defaultMessage: 'Actions',
+        }
+      ),
+      dataType: 'string',
+      render: (name: string) => (
+        <EuiLink {...engineLinkProps(name)}>
+          <FormattedMessage
+            id="xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage"
+            defaultMessage="Manage"
+          />
+        </EuiLink>
+      ),
+      align: 'right',
+      width: '100px',
+    },
+  ];
+
+  return (
+    <EuiBasicTable
+      items={data}
+      columns={columns}
+      pagination={{
+        pageIndex,
+        pageSize: ENGINES_PAGE_SIZE,
+        totalItemCount: totalEngines,
+        hidePerPageOptions: true,
+      }}
+      onChange={({ page }: IOnChange) => {
+        const { index } = page;
+        onPaginate(index + 1); // Note on paging - App Search's API pages start at 1, EuiBasicTables' pages start at 0
+      }}
+    />
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts
new file mode 100644
index 0000000000000..48b7645dc39e8
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { EngineOverview } from './engine_overview';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx
new file mode 100644
index 0000000000000..2e49540270ef0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.test.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import '../../../__mocks__/shallow_usecontext.mock';
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() }));
+import { sendTelemetry } from '../../../shared/telemetry';
+
+import { EngineOverviewHeader } from '../engine_overview_header';
+
+describe('EngineOverviewHeader', () => {
+  it('renders', () => {
+    const wrapper = shallow(<EngineOverviewHeader />);
+    expect(wrapper.find('h1')).toHaveLength(1);
+  });
+
+  it('renders a launch app search button that sends telemetry on click', () => {
+    const wrapper = shallow(<EngineOverviewHeader />);
+    const button = wrapper.find('[data-test-subj="launchButton"]');
+
+    expect(button.prop('href')).toBe('http://localhost:3002/as');
+    expect(button.prop('isDisabled')).toBeFalsy();
+
+    button.simulate('click');
+    expect(sendTelemetry).toHaveBeenCalled();
+  });
+
+  it('renders a disabled button when isButtonDisabled is true', () => {
+    const wrapper = shallow(<EngineOverviewHeader isButtonDisabled />);
+    const button = wrapper.find('[data-test-subj="launchButton"]');
+
+    expect(button.prop('isDisabled')).toBe(true);
+    expect(button.prop('href')).toBeUndefined();
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx
new file mode 100644
index 0000000000000..9aafa8ec0380c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import {
+  EuiPageHeader,
+  EuiPageHeaderSection,
+  EuiTitle,
+  EuiButton,
+  EuiButtonProps,
+  EuiLinkProps,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { sendTelemetry } from '../../../shared/telemetry';
+import { KibanaContext, IKibanaContext } from '../../../index';
+
+interface IEngineOverviewHeaderProps {
+  isButtonDisabled?: boolean;
+}
+
+export const EngineOverviewHeader: React.FC<IEngineOverviewHeaderProps> = ({
+  isButtonDisabled,
+}) => {
+  const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+
+  const buttonProps = {
+    fill: true,
+    iconType: 'popout',
+    'data-test-subj': 'launchButton',
+  } as EuiButtonProps & EuiLinkProps;
+
+  if (isButtonDisabled) {
+    buttonProps.isDisabled = true;
+  } else {
+    buttonProps.href = `${enterpriseSearchUrl}/as`;
+    buttonProps.target = '_blank';
+    buttonProps.onClick = () =>
+      sendTelemetry({
+        http,
+        product: 'app_search',
+        action: 'clicked',
+        metric: 'header_launch_button',
+      });
+  }
+
+  return (
+    <EuiPageHeader>
+      <EuiPageHeaderSection>
+        <EuiTitle size="l">
+          <h1>
+            <FormattedMessage
+              id="xpack.enterpriseSearch.appSearch.enginesOverview.title"
+              defaultMessage="Engine Overview"
+            />
+          </h1>
+        </EuiTitle>
+      </EuiPageHeaderSection>
+      <EuiPageHeaderSection>
+        <EuiButton {...buttonProps}>
+          <FormattedMessage
+            id="xpack.enterpriseSearch.appSearch.productCta"
+            defaultMessage="Launch App Search"
+          />
+        </EuiButton>
+      </EuiPageHeaderSection>
+    </EuiPageHeader>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/index.ts
new file mode 100644
index 0000000000000..2d37f037e21e5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { EngineOverviewHeader } from './engine_overview_header';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts
new file mode 100644
index 0000000000000..c367424d375f9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { SetupGuide } from './setup_guide';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx
new file mode 100644
index 0000000000000..82cc344d49632
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs';
+import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide';
+import { SetupGuide } from './';
+
+describe('SetupGuide', () => {
+  it('renders', () => {
+    const wrapper = shallow(<SetupGuide />);
+
+    expect(wrapper.find(SetupGuideLayout)).toHaveLength(1);
+    expect(wrapper.find(SetBreadcrumbs)).toHaveLength(1);
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx
new file mode 100644
index 0000000000000..df278bf938a69
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+
+import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide';
+import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs';
+import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
+import GettingStarted from '../../assets/getting_started.png';
+
+export const SetupGuide: React.FC = () => (
+  <SetupGuideLayout
+    productName={i18n.translate('xpack.enterpriseSearch.appSearch.productName', {
+      defaultMessage: 'App Search',
+    })}
+    productEuiIcon="logoAppSearch"
+    standardAuthLink="https://swiftype.com/documentation/app-search/self-managed/security#standard"
+    elasticsearchNativeAuthLink="https://swiftype.com/documentation/app-search/self-managed/security#elasticsearch-native-realm"
+  >
+    <SetBreadcrumbs text="Setup Guide" />
+    <SendTelemetry action="viewed" metric="setup_guide" />
+
+    <a
+      href="https://www.elastic.co/webinars/getting-started-with-elastic-app-search"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      <img
+        className="setupGuide__thumbnail"
+        src={GettingStarted}
+        alt={i18n.translate('xpack.enterpriseSearch.appSearch.setupGuide.videoAlt', {
+          defaultMessage:
+            "Getting started with App Search - in this short video we'll guide you through how to get App Search up and running",
+        })}
+        width="1280"
+        height-="720"
+      />
+    </a>
+
+    <EuiTitle size="s">
+      <p>
+        <FormattedMessage
+          id="xpack.enterpriseSearch.appSearch.setupGuide.description"
+          defaultMessage="Elastic App Search provides tools to design and deploy a powerful search to your websites and mobile applications."
+        />
+      </p>
+    </EuiTitle>
+    <EuiSpacer size="m" />
+    <EuiText>
+      <p>
+        <FormattedMessage
+          id="xpack.enterpriseSearch.appSearch.setupGuide.notConfigured"
+          defaultMessage="App Search is not configured in your Kibana instance yet."
+        />
+      </p>
+    </EuiText>
+  </SetupGuideLayout>
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
new file mode 100644
index 0000000000000..45e318ca0f9d9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import '../__mocks__/shallow_usecontext.mock';
+
+import React, { useContext } from 'react';
+import { Redirect } from 'react-router-dom';
+import { shallow } from 'enzyme';
+
+import { SetupGuide } from './components/setup_guide';
+import { EngineOverview } from './components/engine_overview';
+
+import { AppSearch } from './';
+
+describe('App Search Routes', () => {
+  describe('/', () => {
+    it('redirects to Setup Guide when enterpriseSearchUrl is not set', () => {
+      (useContext as jest.Mock).mockImplementationOnce(() => ({ enterpriseSearchUrl: '' }));
+      const wrapper = shallow(<AppSearch />);
+
+      expect(wrapper.find(Redirect)).toHaveLength(1);
+      expect(wrapper.find(EngineOverview)).toHaveLength(0);
+    });
+
+    it('renders Engine Overview when enterpriseSearchUrl is set', () => {
+      (useContext as jest.Mock).mockImplementationOnce(() => ({
+        enterpriseSearchUrl: 'https://foo.bar',
+      }));
+      const wrapper = shallow(<AppSearch />);
+
+      expect(wrapper.find(EngineOverview)).toHaveLength(1);
+      expect(wrapper.find(Redirect)).toHaveLength(0);
+    });
+  });
+
+  describe('/setup_guide', () => {
+    it('renders', () => {
+      const wrapper = shallow(<AppSearch />);
+
+      expect(wrapper.find(SetupGuide)).toHaveLength(1);
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
new file mode 100644
index 0000000000000..8f7142f1631a9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import { Route, Redirect } from 'react-router-dom';
+
+import { KibanaContext, IKibanaContext } from '../index';
+
+import { SetupGuide } from './components/setup_guide';
+import { EngineOverview } from './components/engine_overview';
+
+export const AppSearch: React.FC = () => {
+  const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
+
+  return (
+    <>
+      <Route exact path="/">
+        {!enterpriseSearchUrl ? <Redirect to="/setup_guide" /> : <EngineOverview />}
+      </Route>
+      <Route path="/setup_guide">
+        <SetupGuide />
+      </Route>
+    </>
+  );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
new file mode 100644
index 0000000000000..1aead8468ca3b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { coreMock } from 'src/core/public/mocks';
+import { licensingMock } from '../../../licensing/public/mocks';
+
+import { renderApp } from './';
+import { AppSearch } from './app_search';
+
+describe('renderApp', () => {
+  const params = coreMock.createAppMountParamters();
+  const core = coreMock.createStart();
+  const config = {};
+  const plugins = {
+    licensing: licensingMock.createSetup(),
+  } as any;
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('mounts and unmounts UI', () => {
+    const MockApp = () => <div className="hello-world">Hello world!</div>;
+
+    const unmount = renderApp(MockApp, core, params, config, plugins);
+    expect(params.element.querySelector('.hello-world')).not.toBeNull();
+    unmount();
+    expect(params.element.innerHTML).toEqual('');
+  });
+
+  it('renders AppSearch', () => {
+    renderApp(AppSearch, core, params, config, plugins);
+    expect(params.element.querySelector('.setupGuide')).not.toBeNull();
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx
new file mode 100644
index 0000000000000..4ef7aca8260a2
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Router } from 'react-router-dom';
+
+import { I18nProvider } from '@kbn/i18n/react';
+import { CoreStart, AppMountParameters, HttpSetup, ChromeBreadcrumb } from 'src/core/public';
+import { ClientConfigType, PluginsSetup } from '../plugin';
+import { LicenseProvider } from './shared/licensing';
+
+export interface IKibanaContext {
+  enterpriseSearchUrl?: string;
+  http: HttpSetup;
+  setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
+}
+
+export const KibanaContext = React.createContext({});
+
+/**
+ * This file serves as a reusable wrapper to share Kibana-level context and other helpers
+ * between various Enterprise Search plugins (e.g. AppSearch, WorkplaceSearch, ES landing page)
+ * which should be imported and passed in as the first param in plugin.ts.
+ */
+
+export const renderApp = (
+  App: React.FC,
+  core: CoreStart,
+  params: AppMountParameters,
+  config: ClientConfigType,
+  plugins: PluginsSetup
+) => {
+  ReactDOM.render(
+    <I18nProvider>
+      <KibanaContext.Provider
+        value={{
+          http: core.http,
+          enterpriseSearchUrl: config.host,
+          setBreadcrumbs: core.chrome.setBreadcrumbs,
+        }}
+      >
+        <LicenseProvider license$={plugins.licensing.license$}>
+          <Router history={params.history}>
+            <App />
+          </Router>
+        </LicenseProvider>
+      </KibanaContext.Provider>
+    </I18nProvider>,
+    params.element
+  );
+  return () => ReactDOM.unmountComponentAtNode(params.element);
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.test.ts
new file mode 100644
index 0000000000000..42f308c554268
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.test.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { getPublicUrl } from './';
+
+describe('Enterprise Search URL helper', () => {
+  const httpMock = { get: jest.fn() } as any;
+
+  it('calls and returns the public URL API endpoint', async () => {
+    httpMock.get.mockImplementationOnce(() => ({ publicUrl: 'http://some.vanity.url' }));
+
+    expect(await getPublicUrl(httpMock)).toEqual('http://some.vanity.url');
+  });
+
+  it('strips trailing slashes', async () => {
+    httpMock.get.mockImplementationOnce(() => ({ publicUrl: 'http://trailing.slash/' }));
+
+    expect(await getPublicUrl(httpMock)).toEqual('http://trailing.slash');
+  });
+
+  // For the most part, error logging/handling is done on the server side.
+  // On the front-end, we should simply gracefully fall back to config.host
+  // if we can't fetch a public URL
+  it('falls back to an empty string', async () => {
+    expect(await getPublicUrl(httpMock)).toEqual('');
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.ts
new file mode 100644
index 0000000000000..419c187a0048a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/get_enterprise_search_url.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HttpSetup } from 'src/core/public';
+
+/**
+ * On Elastic Cloud, the host URL set in kibana.yml is not necessarily the same
+ * URL we want to send users to in the front-end (e.g. if a vanity URL is set).
+ *
+ * This helper checks a Kibana API endpoint (which has checks an Enterprise
+ * Search internal API endpoint) for the correct public-facing URL to use.
+ */
+export const getPublicUrl = async (http: HttpSetup): Promise<string> => {
+  try {
+    const { publicUrl } = await http.get('/api/enterprise_search/public_url');
+    return stripTrailingSlash(publicUrl);
+  } catch {
+    return '';
+  }
+};
+
+const stripTrailingSlash = (url: string): string => {
+  return url.endsWith('/') ? url.slice(0, -1) : url;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts
new file mode 100644
index 0000000000000..bbbb688b8ea7b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { getPublicUrl } from './get_enterprise_search_url';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.test.ts
new file mode 100644
index 0000000000000..7ea73577c4de6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.test.ts
@@ -0,0 +1,206 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { generateBreadcrumb } from './generate_breadcrumbs';
+import { appSearchBreadcrumbs, enterpriseSearchBreadcrumbs } from './';
+
+import { mockHistory as mockHistoryUntyped } from '../../__mocks__';
+const mockHistory = mockHistoryUntyped as any;
+
+jest.mock('../react_router_helpers', () => ({ letBrowserHandleEvent: jest.fn(() => false) }));
+import { letBrowserHandleEvent } from '../react_router_helpers';
+
+describe('generateBreadcrumb', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it("creates a breadcrumb object matching EUI's breadcrumb type", () => {
+    const breadcrumb = generateBreadcrumb({
+      text: 'Hello World',
+      path: '/hello_world',
+      history: mockHistory,
+    });
+    expect(breadcrumb).toEqual({
+      text: 'Hello World',
+      href: '/enterprise_search/hello_world',
+      onClick: expect.any(Function),
+    });
+  });
+
+  it('prevents default navigation and uses React Router history on click', () => {
+    const breadcrumb = generateBreadcrumb({ text: '', path: '/', history: mockHistory }) as any;
+    const event = { preventDefault: jest.fn() };
+    breadcrumb.onClick(event);
+
+    expect(mockHistory.push).toHaveBeenCalled();
+    expect(event.preventDefault).toHaveBeenCalled();
+  });
+
+  it('does not prevent default browser behavior on new tab/window clicks', () => {
+    const breadcrumb = generateBreadcrumb({ text: '', path: '/', history: mockHistory }) as any;
+
+    (letBrowserHandleEvent as jest.Mock).mockImplementationOnce(() => true);
+    breadcrumb.onClick();
+
+    expect(mockHistory.push).not.toHaveBeenCalled();
+  });
+
+  it('does not generate link behavior if path is excluded', () => {
+    const breadcrumb = generateBreadcrumb({ text: 'Unclickable breadcrumb' });
+
+    expect(breadcrumb.href).toBeUndefined();
+    expect(breadcrumb.onClick).toBeUndefined();
+  });
+});
+
+describe('enterpriseSearchBreadcrumbs', () => {
+  const breadCrumbs = [
+    {
+      text: 'Page 1',
+      path: '/page1',
+    },
+    {
+      text: 'Page 2',
+      path: '/page2',
+    },
+  ];
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  const subject = () => enterpriseSearchBreadcrumbs(mockHistory)(breadCrumbs);
+
+  it('Builds a chain of breadcrumbs with Enterprise Search at the root', () => {
+    expect(subject()).toEqual([
+      {
+        text: 'Enterprise Search',
+      },
+      {
+        href: '/enterprise_search/page1',
+        onClick: expect.any(Function),
+        text: 'Page 1',
+      },
+      {
+        href: '/enterprise_search/page2',
+        onClick: expect.any(Function),
+        text: 'Page 2',
+      },
+    ]);
+  });
+
+  it('shows just the root if breadcrumbs is empty', () => {
+    expect(enterpriseSearchBreadcrumbs(mockHistory)()).toEqual([
+      {
+        text: 'Enterprise Search',
+      },
+    ]);
+  });
+
+  describe('links', () => {
+    const eventMock = {
+      preventDefault: jest.fn(),
+    } as any;
+
+    it('has Enterprise Search text first', () => {
+      expect(subject()[0].onClick).toBeUndefined();
+    });
+
+    it('has a link to page 1 second', () => {
+      (subject()[1] as any).onClick(eventMock);
+      expect(mockHistory.push).toHaveBeenCalledWith('/page1');
+    });
+
+    it('has a link to page 2 last', () => {
+      (subject()[2] as any).onClick(eventMock);
+      expect(mockHistory.push).toHaveBeenCalledWith('/page2');
+    });
+  });
+});
+
+describe('appSearchBreadcrumbs', () => {
+  const breadCrumbs = [
+    {
+      text: 'Page 1',
+      path: '/page1',
+    },
+    {
+      text: 'Page 2',
+      path: '/page2',
+    },
+  ];
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+    mockHistory.createHref.mockImplementation(
+      ({ pathname }: any) => `/enterprise_search/app_search${pathname}`
+    );
+  });
+
+  const subject = () => appSearchBreadcrumbs(mockHistory)(breadCrumbs);
+
+  it('Builds a chain of breadcrumbs with Enterprise Search and App Search at the root', () => {
+    expect(subject()).toEqual([
+      {
+        text: 'Enterprise Search',
+      },
+      {
+        href: '/enterprise_search/app_search/',
+        onClick: expect.any(Function),
+        text: 'App Search',
+      },
+      {
+        href: '/enterprise_search/app_search/page1',
+        onClick: expect.any(Function),
+        text: 'Page 1',
+      },
+      {
+        href: '/enterprise_search/app_search/page2',
+        onClick: expect.any(Function),
+        text: 'Page 2',
+      },
+    ]);
+  });
+
+  it('shows just the root if breadcrumbs is empty', () => {
+    expect(appSearchBreadcrumbs(mockHistory)()).toEqual([
+      {
+        text: 'Enterprise Search',
+      },
+      {
+        href: '/enterprise_search/app_search/',
+        onClick: expect.any(Function),
+        text: 'App Search',
+      },
+    ]);
+  });
+
+  describe('links', () => {
+    const eventMock = {
+      preventDefault: jest.fn(),
+    } as any;
+
+    it('has Enterprise Search text first', () => {
+      expect(subject()[0].onClick).toBeUndefined();
+    });
+
+    it('has a link to App Search second', () => {
+      (subject()[1] as any).onClick(eventMock);
+      expect(mockHistory.push).toHaveBeenCalledWith('/');
+    });
+
+    it('has a link to page 1 third', () => {
+      (subject()[2] as any).onClick(eventMock);
+      expect(mockHistory.push).toHaveBeenCalledWith('/page1');
+    });
+
+    it('has a link to page 2 last', () => {
+      (subject()[3] as any).onClick(eventMock);
+      expect(mockHistory.push).toHaveBeenCalledWith('/page2');
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts
new file mode 100644
index 0000000000000..0e1bb796cbf2e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/generate_breadcrumbs.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Breadcrumb as EuiBreadcrumb } from '@elastic/eui';
+import { History } from 'history';
+
+import { letBrowserHandleEvent } from '../react_router_helpers';
+
+/**
+ * Generate React-Router-friendly EUI breadcrumb objects
+ * https://elastic.github.io/eui/#/navigation/breadcrumbs
+ */
+
+interface IGenerateBreadcrumbProps {
+  text: string;
+  path?: string;
+  history?: History;
+}
+
+export const generateBreadcrumb = ({ text, path, history }: IGenerateBreadcrumbProps) => {
+  const breadcrumb = { text } as EuiBreadcrumb;
+
+  if (path && history) {
+    breadcrumb.href = history.createHref({ pathname: path });
+    breadcrumb.onClick = (event) => {
+      if (letBrowserHandleEvent(event)) return;
+      event.preventDefault();
+      history.push(path);
+    };
+  }
+
+  return breadcrumb;
+};
+
+/**
+ * Product-specific breadcrumb helpers
+ */
+
+export type TBreadcrumbs = IGenerateBreadcrumbProps[];
+
+export const enterpriseSearchBreadcrumbs = (history: History) => (
+  breadcrumbs: TBreadcrumbs = []
+) => [
+  generateBreadcrumb({ text: 'Enterprise Search' }),
+  ...breadcrumbs.map(({ text, path }: IGenerateBreadcrumbProps) =>
+    generateBreadcrumb({ text, path, history })
+  ),
+];
+
+export const appSearchBreadcrumbs = (history: History) => (breadcrumbs: TBreadcrumbs = []) =>
+  enterpriseSearchBreadcrumbs(history)([{ text: 'App Search', path: '/' }, ...breadcrumbs]);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/index.ts
new file mode 100644
index 0000000000000..cf8bbbc593f2f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { enterpriseSearchBreadcrumbs } from './generate_breadcrumbs';
+export { appSearchBreadcrumbs } from './generate_breadcrumbs';
+export { SetAppSearchBreadcrumbs } from './set_breadcrumbs';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.test.tsx
new file mode 100644
index 0000000000000..974ca54277c51
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.test.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import '../../__mocks__/react_router_history.mock';
+import { mountWithKibanaContext } from '../../__mocks__';
+
+jest.mock('./generate_breadcrumbs', () => ({ appSearchBreadcrumbs: jest.fn() }));
+import { appSearchBreadcrumbs, SetAppSearchBreadcrumbs } from './';
+
+describe('SetAppSearchBreadcrumbs', () => {
+  const setBreadcrumbs = jest.fn();
+  const builtBreadcrumbs = [] as any;
+  const appSearchBreadCrumbsInnerCall = jest.fn().mockReturnValue(builtBreadcrumbs);
+  const appSearchBreadCrumbsOuterCall = jest.fn().mockReturnValue(appSearchBreadCrumbsInnerCall);
+  (appSearchBreadcrumbs as jest.Mock).mockImplementation(appSearchBreadCrumbsOuterCall);
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  const mountSetAppSearchBreadcrumbs = (props: any) => {
+    return mountWithKibanaContext(<SetAppSearchBreadcrumbs {...props} />, {
+      http: {},
+      enterpriseSearchUrl: 'http://localhost:3002',
+      setBreadcrumbs,
+    });
+  };
+
+  describe('when isRoot is false', () => {
+    const subject = () => mountSetAppSearchBreadcrumbs({ text: 'Page 1', isRoot: false });
+
+    it('calls appSearchBreadcrumbs to build breadcrumbs, then registers them with Kibana', () => {
+      subject();
+
+      // calls appSearchBreadcrumbs to build breadcrumbs with the target page and current location
+      expect(appSearchBreadCrumbsInnerCall).toHaveBeenCalledWith([
+        { text: 'Page 1', path: '/current-path' },
+      ]);
+
+      // then registers them with Kibana
+      expect(setBreadcrumbs).toHaveBeenCalledWith(builtBreadcrumbs);
+    });
+  });
+
+  describe('when isRoot is true', () => {
+    const subject = () => mountSetAppSearchBreadcrumbs({ text: 'Page 1', isRoot: true });
+
+    it('calls appSearchBreadcrumbs to build breadcrumbs with an empty breadcrumb, then registers them with Kibana', () => {
+      subject();
+
+      // uses an empty bredcrumb
+      expect(appSearchBreadCrumbsInnerCall).toHaveBeenCalledWith([]);
+
+      // then registers them with Kibana
+      expect(setBreadcrumbs).toHaveBeenCalledWith(builtBreadcrumbs);
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx
new file mode 100644
index 0000000000000..ad3cd65c09516
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_breadcrumbs/set_breadcrumbs.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext, useEffect } from 'react';
+import { useHistory } from 'react-router-dom';
+import { Breadcrumb as EuiBreadcrumb } from '@elastic/eui';
+import { KibanaContext, IKibanaContext } from '../../index';
+import { appSearchBreadcrumbs, TBreadcrumbs } from './generate_breadcrumbs';
+
+/**
+ * Small on-mount helper for setting Kibana's chrome breadcrumbs on any App Search view
+ * @see https://github.com/elastic/kibana/blob/master/src/core/public/chrome/chrome_service.tsx
+ */
+
+export type TSetBreadcrumbs = (breadcrumbs: EuiBreadcrumb[]) => void;
+
+interface IBreadcrumbProps {
+  text: string;
+  isRoot?: never;
+}
+interface IRootBreadcrumbProps {
+  isRoot: true;
+  text?: never;
+}
+
+export const SetAppSearchBreadcrumbs: React.FC<IBreadcrumbProps | IRootBreadcrumbProps> = ({
+  text,
+  isRoot,
+}) => {
+  const history = useHistory();
+  const { setBreadcrumbs } = useContext(KibanaContext) as IKibanaContext;
+
+  const crumb = isRoot ? [] : [{ text, path: history.location.pathname }];
+
+  useEffect(() => {
+    setBreadcrumbs(appSearchBreadcrumbs(history)(crumb as TBreadcrumbs | []));
+  }, []);
+
+  return null;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts
new file mode 100644
index 0000000000000..9c8c1417d48db
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context';
+export { hasPlatinumLicense } from './license_checks';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts
new file mode 100644
index 0000000000000..ad134e7d36b10
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { hasPlatinumLicense } from './license_checks';
+
+describe('hasPlatinumLicense', () => {
+  it('is true for platinum licenses', () => {
+    expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true);
+  });
+
+  it('is true for enterprise licenses', () => {
+    expect(hasPlatinumLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true);
+  });
+
+  it('is true for trial licenses', () => {
+    expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true);
+  });
+
+  it('is false if the current license is expired', () => {
+    expect(hasPlatinumLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false);
+    expect(hasPlatinumLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false);
+    expect(hasPlatinumLicense({ isActive: false, type: 'trial' } as any)).toEqual(false);
+  });
+
+  it('is false for licenses below platinum', () => {
+    expect(hasPlatinumLicense({ isActive: true, type: 'basic' } as any)).toEqual(false);
+    expect(hasPlatinumLicense({ isActive: false, type: 'standard' } as any)).toEqual(false);
+    expect(hasPlatinumLicense({ isActive: true, type: 'gold' } as any)).toEqual(false);
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts
new file mode 100644
index 0000000000000..de4a17ce2bd3c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { ILicense } from '../../../../../licensing/public';
+
+export const hasPlatinumLicense = (license?: ILicense) => {
+  return license?.isActive && ['platinum', 'enterprise', 'trial'].includes(license?.type as string);
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx
new file mode 100644
index 0000000000000..c65474ec1f590
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+
+import { mountWithContext } from '../../__mocks__';
+import { LicenseContext, ILicenseContext } from './';
+
+describe('LicenseProvider', () => {
+  const MockComponent: React.FC = () => {
+    const { license } = useContext(LicenseContext) as ILicenseContext;
+    return <div className="license-test">{license?.type}</div>;
+  };
+
+  it('renders children', () => {
+    const wrapper = mountWithContext(<MockComponent />, { license: { type: 'basic' } });
+
+    expect(wrapper.find('.license-test')).toHaveLength(1);
+    expect(wrapper.text()).toEqual('basic');
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx
new file mode 100644
index 0000000000000..9b47959ff7544
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import useObservable from 'react-use/lib/useObservable';
+import { Observable } from 'rxjs';
+
+import { ILicense } from '../../../../../licensing/public';
+
+export interface ILicenseContext {
+  license: ILicense;
+}
+interface ILicenseContextProps {
+  license$: Observable<ILicense>;
+  children: React.ReactNode;
+}
+
+export const LicenseContext = React.createContext({});
+
+export const LicenseProvider: React.FC<ILicenseContextProps> = ({ license$, children }) => {
+  // Listen for changes to license subscription
+  const license = useObservable(license$);
+
+  // Render rest of application and pass down license via context
+  return <LicenseContext.Provider value={{ license }} children={children} />;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx
new file mode 100644
index 0000000000000..7d4c068b21155
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx
@@ -0,0 +1,77 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import { EuiLink, EuiButton } from '@elastic/eui';
+
+import '../../__mocks__/react_router_history.mock';
+import { mockHistory } from '../../__mocks__';
+
+import { EuiReactRouterLink, EuiReactRouterButton } from './eui_link';
+
+describe('EUI & React Router Component Helpers', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('renders', () => {
+    const wrapper = shallow(<EuiReactRouterLink to="/" />);
+
+    expect(wrapper.find(EuiLink)).toHaveLength(1);
+  });
+
+  it('renders an EuiButton', () => {
+    const wrapper = shallow(<EuiReactRouterButton to="/" />);
+
+    expect(wrapper.find(EuiButton)).toHaveLength(1);
+  });
+
+  it('passes down all ...rest props', () => {
+    const wrapper = shallow(<EuiReactRouterLink to="/" data-test-subj="foo" external={true} />);
+    const link = wrapper.find(EuiLink);
+
+    expect(link.prop('external')).toEqual(true);
+    expect(link.prop('data-test-subj')).toEqual('foo');
+  });
+
+  it('renders with the correct href and onClick props', () => {
+    const wrapper = mount(<EuiReactRouterLink to="/foo/bar" />);
+    const link = wrapper.find(EuiLink);
+
+    expect(link.prop('onClick')).toBeInstanceOf(Function);
+    expect(link.prop('href')).toEqual('/enterprise_search/foo/bar');
+    expect(mockHistory.createHref).toHaveBeenCalled();
+  });
+
+  describe('onClick', () => {
+    it('prevents default navigation and uses React Router history', () => {
+      const wrapper = mount(<EuiReactRouterLink to="/bar/baz" />);
+
+      const simulatedEvent = {
+        button: 0,
+        target: { getAttribute: () => '_self' },
+        preventDefault: jest.fn(),
+      };
+      wrapper.find(EuiLink).simulate('click', simulatedEvent);
+
+      expect(simulatedEvent.preventDefault).toHaveBeenCalled();
+      expect(mockHistory.push).toHaveBeenCalled();
+    });
+
+    it('does not prevent default browser behavior on new tab/window clicks', () => {
+      const wrapper = mount(<EuiReactRouterLink to="/bar/baz" />);
+
+      const simulatedEvent = {
+        shiftKey: true,
+        target: { getAttribute: () => '_blank' },
+      };
+      wrapper.find(EuiLink).simulate('click', simulatedEvent);
+
+      expect(mockHistory.push).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx
new file mode 100644
index 0000000000000..f486e432bae76
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { EuiLink, EuiButton, EuiButtonProps, EuiLinkAnchorProps } from '@elastic/eui';
+
+import { letBrowserHandleEvent } from './link_events';
+
+/**
+ * Generates either an EuiLink or EuiButton with a React-Router-ified link
+ *
+ * Based off of EUI's recommendations for handling React Router:
+ * https://github.com/elastic/eui/blob/master/wiki/react-router.md#react-router-51
+ */
+
+interface IEuiReactRouterProps {
+  to: string;
+}
+
+export const EuiReactRouterHelper: React.FC<IEuiReactRouterProps> = ({ to, children }) => {
+  const history = useHistory();
+
+  const onClick = (event: React.MouseEvent) => {
+    if (letBrowserHandleEvent(event)) return;
+
+    // Prevent regular link behavior, which causes a browser refresh.
+    event.preventDefault();
+
+    // Push the route to the history.
+    history.push(to);
+  };
+
+  // Generate the correct link href (with basename etc. accounted for)
+  const href = history.createHref({ pathname: to });
+
+  const reactRouterProps = { href, onClick };
+  return React.cloneElement(children as React.ReactElement, reactRouterProps);
+};
+
+type TEuiReactRouterLinkProps = EuiLinkAnchorProps & IEuiReactRouterProps;
+type TEuiReactRouterButtonProps = EuiButtonProps & IEuiReactRouterProps;
+
+export const EuiReactRouterLink: React.FC<TEuiReactRouterLinkProps> = ({ to, ...rest }) => (
+  <EuiReactRouterHelper to={to}>
+    <EuiLink {...rest} />
+  </EuiReactRouterHelper>
+);
+
+export const EuiReactRouterButton: React.FC<TEuiReactRouterButtonProps> = ({ to, ...rest }) => (
+  <EuiReactRouterHelper to={to}>
+    <EuiButton {...rest} />
+  </EuiReactRouterHelper>
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts
new file mode 100644
index 0000000000000..46dc328633153
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { letBrowserHandleEvent } from './link_events';
+export { EuiReactRouterLink as EuiLink } from './eui_link';
+export { EuiReactRouterButton as EuiButton } from './eui_link';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.test.ts
new file mode 100644
index 0000000000000..3682946b63a13
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.test.ts
@@ -0,0 +1,102 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { letBrowserHandleEvent } from '../react_router_helpers';
+
+describe('letBrowserHandleEvent', () => {
+  const event = {
+    defaultPrevented: false,
+    metaKey: false,
+    altKey: false,
+    ctrlKey: false,
+    shiftKey: false,
+    button: 0,
+    target: {
+      getAttribute: () => '_self',
+    },
+  } as any;
+
+  describe('the browser should handle the link when', () => {
+    it('default is prevented', () => {
+      expect(letBrowserHandleEvent({ ...event, defaultPrevented: true })).toBe(true);
+    });
+
+    it('is modified with metaKey', () => {
+      expect(letBrowserHandleEvent({ ...event, metaKey: true })).toBe(true);
+    });
+
+    it('is modified with altKey', () => {
+      expect(letBrowserHandleEvent({ ...event, altKey: true })).toBe(true);
+    });
+
+    it('is modified with ctrlKey', () => {
+      expect(letBrowserHandleEvent({ ...event, ctrlKey: true })).toBe(true);
+    });
+
+    it('is modified with shiftKey', () => {
+      expect(letBrowserHandleEvent({ ...event, shiftKey: true })).toBe(true);
+    });
+
+    it('it is not a left click event', () => {
+      expect(letBrowserHandleEvent({ ...event, button: 2 })).toBe(true);
+    });
+
+    it('the target is anything value other than _self', () => {
+      expect(
+        letBrowserHandleEvent({
+          ...event,
+          target: targetValue('_blank'),
+        })
+      ).toBe(true);
+    });
+  });
+
+  describe('the browser should NOT handle the link when', () => {
+    it('default is not prevented', () => {
+      expect(letBrowserHandleEvent({ ...event, defaultPrevented: false })).toBe(false);
+    });
+
+    it('is not modified', () => {
+      expect(
+        letBrowserHandleEvent({
+          ...event,
+          metaKey: false,
+          altKey: false,
+          ctrlKey: false,
+          shiftKey: false,
+        })
+      ).toBe(false);
+    });
+
+    it('it is a left click event', () => {
+      expect(letBrowserHandleEvent({ ...event, button: 0 })).toBe(false);
+    });
+
+    it('the target is a value of _self', () => {
+      expect(
+        letBrowserHandleEvent({
+          ...event,
+          target: targetValue('_self'),
+        })
+      ).toBe(false);
+    });
+
+    it('the target has no value', () => {
+      expect(
+        letBrowserHandleEvent({
+          ...event,
+          target: targetValue(null),
+        })
+      ).toBe(false);
+    });
+  });
+});
+
+const targetValue = (value: string | null) => {
+  return {
+    getAttribute: () => value,
+  };
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.ts
new file mode 100644
index 0000000000000..93da2ab71d952
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/link_events.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { MouseEvent } from 'react';
+
+/**
+ * Helper functions for determining which events we should
+ * let browsers handle natively, e.g. new tabs/windows
+ */
+
+type THandleEvent = (event: MouseEvent) => boolean;
+
+export const letBrowserHandleEvent: THandleEvent = (event) =>
+  event.defaultPrevented ||
+  isModifiedEvent(event) ||
+  !isLeftClickEvent(event) ||
+  isTargetBlank(event);
+
+const isModifiedEvent: THandleEvent = (event) =>
+  !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
+
+const isLeftClickEvent: THandleEvent = (event) => event.button === 0;
+
+const isTargetBlank: THandleEvent = (event) => {
+  const element = event.target as HTMLElement;
+  const target = element.getAttribute('target');
+  return !!target && target !== '_self';
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/index.ts
new file mode 100644
index 0000000000000..c367424d375f9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { SetupGuide } from './setup_guide';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.scss b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.scss
new file mode 100644
index 0000000000000..ecfa13cc828f0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.scss
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * Setup Guide
+ */
+.setupGuide {
+  padding: 0;
+  min-height: 100vh;
+
+  &__sidebar {
+    flex-basis: $euiSizeXXL * 7.5;
+    flex-shrink: 0;
+    padding: $euiSizeL;
+    margin-right: 0;
+
+    background-color: $euiColorLightestShade;
+    border-color: $euiBorderColor;
+    border-style: solid;
+    border-width: 0 0 $euiBorderWidthThin 0; // bottom - mobile view
+
+    @include euiBreakpoint('m', 'l', 'xl') {
+      border-width: 0 $euiBorderWidthThin 0 0; // right - desktop view
+    }
+    @include euiBreakpoint('m', 'l') {
+      flex-basis: $euiSizeXXL * 10;
+    }
+    @include euiBreakpoint('xl') {
+      flex-basis: $euiSizeXXL * 12.5;
+    }
+  }
+
+  &__body {
+    align-self: start;
+    padding: $euiSizeL;
+
+    @include euiBreakpoint('l') {
+      padding: $euiSizeXXL ($euiSizeXXL * 1.25);
+    }
+  }
+
+  &__thumbnail {
+    display: block;
+    max-width: 100%;
+    height: auto;
+    margin: $euiSizeL auto;
+  }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.test.tsx
new file mode 100644
index 0000000000000..0423ae61779af
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.test.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { EuiSteps, EuiIcon, EuiLink } from '@elastic/eui';
+
+import { mountWithContext } from '../../__mocks__';
+
+import { SetupGuide } from './';
+
+describe('SetupGuide', () => {
+  it('renders', () => {
+    const wrapper = shallow(
+      <SetupGuide productName="Enterprise Search" productEuiIcon="logoEnterpriseSearch">
+        <p data-test-subj="test">Wow!</p>
+      </SetupGuide>
+    );
+
+    expect(wrapper.find('h1').text()).toEqual('Enterprise Search');
+    expect(wrapper.find(EuiIcon).prop('type')).toEqual('logoEnterpriseSearch');
+    expect(wrapper.find('[data-test-subj="test"]').text()).toEqual('Wow!');
+    expect(wrapper.find(EuiSteps)).toHaveLength(1);
+  });
+
+  it('renders with optional auth links', () => {
+    const wrapper = mountWithContext(
+      <SetupGuide
+        productName="Foo"
+        productEuiIcon="logoAppSearch"
+        standardAuthLink="http://foo.com"
+        elasticsearchNativeAuthLink="http://bar.com"
+      >
+        Baz
+      </SetupGuide>
+    );
+
+    expect(wrapper.find(EuiLink).first().prop('href')).toEqual('http://bar.com');
+    expect(wrapper.find(EuiLink).last().prop('href')).toEqual('http://foo.com');
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.tsx
new file mode 100644
index 0000000000000..31ff0089dbd7c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/setup_guide.tsx
@@ -0,0 +1,226 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import {
+  EuiPage,
+  EuiPageSideBar,
+  EuiPageBody,
+  EuiPageContent,
+  EuiSpacer,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiTitle,
+  EuiText,
+  EuiIcon,
+  EuiSteps,
+  EuiCode,
+  EuiCodeBlock,
+  EuiAccordion,
+  EuiLink,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+
+import './setup_guide.scss';
+
+/**
+ * Shared Setup Guide component. Sidebar content and product name/links are
+ * customizable, but the basic layout and instruction steps are DRYed out
+ */
+
+interface ISetupGuideProps {
+  children: React.ReactNode;
+  productName: string;
+  productEuiIcon: 'logoAppSearch' | 'logoWorkplaceSearch' | 'logoEnterpriseSearch';
+  standardAuthLink?: string;
+  elasticsearchNativeAuthLink?: string;
+}
+
+export const SetupGuide: React.FC<ISetupGuideProps> = ({
+  children,
+  productName,
+  productEuiIcon,
+  standardAuthLink,
+  elasticsearchNativeAuthLink,
+}) => (
+  <EuiPage className="setupGuide">
+    <EuiPageSideBar className="setupGuide__sidebar">
+      <EuiText color="subdued" size="s">
+        <strong>
+          <FormattedMessage
+            id="xpack.enterpriseSearch.setupGuide.title"
+            defaultMessage="Setup Guide"
+          />
+        </strong>
+      </EuiText>
+      <EuiSpacer size="s" />
+
+      <EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
+        <EuiFlexItem grow={false}>
+          <EuiIcon type={productEuiIcon} size="l" />
+        </EuiFlexItem>
+        <EuiFlexItem>
+          <EuiTitle size="m">
+            <h1>{productName}</h1>
+          </EuiTitle>
+        </EuiFlexItem>
+      </EuiFlexGroup>
+
+      {children}
+    </EuiPageSideBar>
+
+    <EuiPageBody className="setupGuide__body">
+      <EuiPageContent>
+        <EuiSteps
+          headingElement="h2"
+          steps={[
+            {
+              title: i18n.translate('xpack.enterpriseSearch.setupGuide.step1.title', {
+                defaultMessage: 'Add your {productName} host URL to your Kibana configuration',
+                values: { productName },
+              }),
+              children: (
+                <EuiText>
+                  <p>
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.setupGuide.step1.instruction1"
+                      defaultMessage="In your {configFile} file, set {configSetting} to the URL of your {productName} instance. For example:"
+                      values={{
+                        productName,
+                        configFile: <EuiCode>config/kibana.yml</EuiCode>,
+                        configSetting: <EuiCode>enterpriseSearch.host</EuiCode>,
+                      }}
+                    />
+                  </p>
+                  <EuiCodeBlock language="yml">
+                    enterpriseSearch.host: &apos;http://localhost:3002&apos;
+                  </EuiCodeBlock>
+                </EuiText>
+              ),
+            },
+            {
+              title: i18n.translate('xpack.enterpriseSearch.setupGuide.step2.title', {
+                defaultMessage: 'Reload your Kibana instance',
+              }),
+              children: (
+                <EuiText>
+                  <p>
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.setupGuide.step2.instruction1"
+                      defaultMessage="Restart Kibana to pick up the configuration changes from the previous step."
+                    />
+                  </p>
+                  <p>
+                    <FormattedMessage
+                      id="xpack.enterpriseSearch.setupGuide.step2.instruction2"
+                      defaultMessage="If you’re using {elasticsearchNativeAuthLink} in {productName}, you’re all set. Your users can now access {productName} in Kibana with their current {productName} access and permissions."
+                      values={{
+                        productName,
+                        elasticsearchNativeAuthLink: elasticsearchNativeAuthLink ? (
+                          <EuiLink href={elasticsearchNativeAuthLink} target="_blank">
+                            Elasticsearch Native Auth
+                          </EuiLink>
+                        ) : (
+                          'Elasticsearch Native Auth'
+                        ),
+                      }}
+                    />
+                  </p>
+                </EuiText>
+              ),
+            },
+            {
+              title: i18n.translate('xpack.enterpriseSearch.setupGuide.step3.title', {
+                defaultMessage: 'Troubleshooting issues',
+              }),
+              children: (
+                <>
+                  <EuiAccordion
+                    buttonContent={i18n.translate(
+                      'xpack.enterpriseSearch.troubleshooting.differentEsClusters.title',
+                      {
+                        defaultMessage:
+                          '{productName} and Kibana are on different Elasticsearch clusters',
+                        values: { productName },
+                      }
+                    )}
+                    id="differentEsClusters"
+                    paddingSize="s"
+                  >
+                    <EuiText>
+                      <p>
+                        <FormattedMessage
+                          id="xpack.enterpriseSearch.troubleshooting.differentEsClusters.description"
+                          defaultMessage="This plugin does not currently support {productName} and Kibana running on different clusters."
+                          values={{ productName }}
+                        />
+                      </p>
+                    </EuiText>
+                  </EuiAccordion>
+                  <EuiSpacer />
+                  <EuiAccordion
+                    buttonContent={i18n.translate(
+                      'xpack.enterpriseSearch.troubleshooting.differentAuth.title',
+                      {
+                        defaultMessage:
+                          '{productName} and Kibana are on different authentication methods',
+                        values: { productName },
+                      }
+                    )}
+                    id="differentAuth"
+                    paddingSize="s"
+                  >
+                    <EuiText>
+                      <p>
+                        <FormattedMessage
+                          id="xpack.enterpriseSearch.troubleshooting.differentAuth.description"
+                          defaultMessage="This plugin does not currently support {productName} and Kibana operating on different authentication methods, for example, {productName} using a different SAML provider than Kibana."
+                          values={{ productName }}
+                        />
+                      </p>
+                    </EuiText>
+                  </EuiAccordion>
+                  <EuiSpacer />
+                  <EuiAccordion
+                    buttonContent={i18n.translate(
+                      'xpack.enterpriseSearch.troubleshooting.standardAuth.title',
+                      {
+                        defaultMessage: '{productName} on Standard authentication is not supported',
+                        values: { productName },
+                      }
+                    )}
+                    id="standardAuth"
+                    paddingSize="s"
+                  >
+                    <EuiText>
+                      <p>
+                        <FormattedMessage
+                          id="xpack.enterpriseSearch.troubleshooting.standardAuth.description"
+                          defaultMessage="This plugin does not fully support {productName} on {standardAuthLink}. Users created in {productName} must have Kibana access. Users created in Kibana will not see {productName} in the navigation menu."
+                          values={{
+                            productName,
+                            standardAuthLink: standardAuthLink ? (
+                              <EuiLink href={standardAuthLink} target="_blank">
+                                Standard Auth
+                              </EuiLink>
+                            ) : (
+                              'Standard Auth'
+                            ),
+                          }}
+                        />
+                      </p>
+                    </EuiText>
+                  </EuiAccordion>
+                </>
+              ),
+            },
+          ]}
+        />
+      </EuiPageContent>
+    </EuiPageBody>
+  </EuiPage>
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/index.ts
new file mode 100644
index 0000000000000..f871f48b17154
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { sendTelemetry } from './send_telemetry';
+export { SendAppSearchTelemetry } from './send_telemetry';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx
new file mode 100644
index 0000000000000..9825c0d8ab889
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { httpServiceMock } from 'src/core/public/mocks';
+import { mountWithKibanaContext } from '../../__mocks__';
+import { sendTelemetry, SendAppSearchTelemetry } from './';
+
+describe('Shared Telemetry Helpers', () => {
+  const httpMock = httpServiceMock.createSetupContract();
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  describe('sendTelemetry', () => {
+    it('successfully calls the server-side telemetry endpoint', () => {
+      sendTelemetry({
+        http: httpMock,
+        product: 'enterprise_search',
+        action: 'viewed',
+        metric: 'setup_guide',
+      });
+
+      expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', {
+        headers: { 'Content-Type': 'application/json' },
+        body: '{"action":"viewed","metric":"setup_guide"}',
+      });
+    });
+
+    it('throws an error if the telemetry endpoint fails', () => {
+      const httpRejectMock = sendTelemetry({
+        http: { put: () => Promise.reject() },
+      } as any);
+
+      expect(httpRejectMock).rejects.toThrow('Unable to send telemetry');
+    });
+  });
+
+  describe('React component helpers', () => {
+    it('SendAppSearchTelemetry component', () => {
+      mountWithKibanaContext(<SendAppSearchTelemetry action="clicked" metric="button" />, {
+        http: httpMock,
+      });
+
+      expect(httpMock.put).toHaveBeenCalledWith('/api/app_search/telemetry', {
+        headers: { 'Content-Type': 'application/json' },
+        body: '{"action":"clicked","metric":"button"}',
+      });
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx
new file mode 100644
index 0000000000000..300cb18272717
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext, useEffect } from 'react';
+
+import { HttpSetup } from 'src/core/public';
+import { KibanaContext, IKibanaContext } from '../../index';
+
+interface ISendTelemetryProps {
+  action: 'viewed' | 'error' | 'clicked';
+  metric: string; // e.g., 'setup_guide'
+}
+
+interface ISendTelemetry extends ISendTelemetryProps {
+  http: HttpSetup;
+  product: 'app_search' | 'workplace_search' | 'enterprise_search';
+}
+
+/**
+ * Base function - useful for non-component actions, e.g. clicks
+ */
+
+export const sendTelemetry = async ({ http, product, action, metric }: ISendTelemetry) => {
+  try {
+    await http.put(`/api/${product}/telemetry`, {
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ action, metric }),
+    });
+  } catch (error) {
+    throw new Error('Unable to send telemetry');
+  }
+};
+
+/**
+ * React component helpers - useful for on-page-load/views
+ * TODO: SendWorkplaceSearchTelemetry and SendEnterpriseSearchTelemetry
+ */
+
+export const SendAppSearchTelemetry: React.FC<ISendTelemetryProps> = ({ action, metric }) => {
+  const { http } = useContext(KibanaContext) as IKibanaContext;
+
+  useEffect(() => {
+    sendTelemetry({ http, action, metric, product: 'app_search' });
+  }, [action, metric, http]);
+
+  return null;
+};
diff --git a/x-pack/plugins/enterprise_search/public/index.ts b/x-pack/plugins/enterprise_search/public/index.ts
new file mode 100644
index 0000000000000..06272641b1929
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { PluginInitializerContext } from 'src/core/public';
+import { EnterpriseSearchPlugin } from './plugin';
+
+export const plugin = (initializerContext: PluginInitializerContext) => {
+  return new EnterpriseSearchPlugin(initializerContext);
+};
diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts
new file mode 100644
index 0000000000000..fbfcc303de47a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/plugin.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import {
+  Plugin,
+  PluginInitializerContext,
+  CoreSetup,
+  CoreStart,
+  AppMountParameters,
+  HttpSetup,
+} from 'src/core/public';
+
+import {
+  FeatureCatalogueCategory,
+  HomePublicPluginSetup,
+} from '../../../../src/plugins/home/public';
+import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
+import { LicensingPluginSetup } from '../../licensing/public';
+
+import { getPublicUrl } from './applications/shared/enterprise_search_url';
+import AppSearchLogo from './applications/app_search/assets/logo.svg';
+
+export interface ClientConfigType {
+  host?: string;
+}
+export interface PluginsSetup {
+  home: HomePublicPluginSetup;
+  licensing: LicensingPluginSetup;
+}
+
+export class EnterpriseSearchPlugin implements Plugin {
+  private config: ClientConfigType;
+  private hasCheckedPublicUrl: boolean = false;
+
+  constructor(initializerContext: PluginInitializerContext) {
+    this.config = initializerContext.config.get<ClientConfigType>();
+  }
+
+  public setup(core: CoreSetup, plugins: PluginsSetup) {
+    const config = { host: this.config.host };
+
+    core.application.register({
+      id: 'appSearch',
+      title: 'App Search',
+      appRoute: '/app/enterprise_search/app_search',
+      category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
+      mount: async (params: AppMountParameters) => {
+        const [coreStart] = await core.getStartServices();
+
+        await this.setPublicUrl(config, coreStart.http);
+
+        const { renderApp } = await import('./applications');
+        const { AppSearch } = await import('./applications/app_search');
+
+        return renderApp(AppSearch, coreStart, params, config, plugins);
+      },
+    });
+    // TODO: Workplace Search will need to register its own plugin.
+
+    plugins.home.featureCatalogue.register({
+      id: 'appSearch',
+      title: 'App Search',
+      icon: AppSearchLogo,
+      description:
+        'Leverage dashboards, analytics, and APIs for advanced application search made simple.',
+      path: '/app/enterprise_search/app_search',
+      category: FeatureCatalogueCategory.DATA,
+      showOnHomePage: true,
+    });
+    // TODO: Workplace Search will need to register its own feature catalogue section/card.
+  }
+
+  public start(core: CoreStart) {}
+
+  public stop() {}
+
+  private async setPublicUrl(config: ClientConfigType, http: HttpSetup) {
+    if (!config.host) return; // No API to check
+    if (this.hasCheckedPublicUrl) return; // We've already performed the check
+
+    const publicUrl = await getPublicUrl(http);
+    if (publicUrl) config.host = publicUrl;
+    this.hasCheckedPublicUrl = true;
+  }
+}
diff --git a/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts
new file mode 100644
index 0000000000000..e95056b871324
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts
@@ -0,0 +1,143 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { loggingSystemMock } from 'src/core/server/mocks';
+
+jest.mock('../../../../../../src/core/server', () => ({
+  SavedObjectsErrorHelpers: {
+    isNotFoundError: jest.fn(),
+  },
+}));
+import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
+
+import { registerTelemetryUsageCollector, incrementUICounter } from './telemetry';
+
+describe('App Search Telemetry Usage Collector', () => {
+  const mockLogger = loggingSystemMock.create().get();
+
+  const makeUsageCollectorStub = jest.fn();
+  const registerStub = jest.fn();
+  const usageCollectionMock = {
+    makeUsageCollector: makeUsageCollectorStub,
+    registerCollector: registerStub,
+  } as any;
+
+  const savedObjectsRepoStub = {
+    get: () => ({
+      attributes: {
+        'ui_viewed.setup_guide': 10,
+        'ui_viewed.engines_overview': 20,
+        'ui_error.cannot_connect': 3,
+        'ui_clicked.create_first_engine_button': 40,
+        'ui_clicked.header_launch_button': 50,
+        'ui_clicked.engine_table_link': 60,
+      },
+    }),
+    incrementCounter: jest.fn(),
+  };
+  const savedObjectsMock = {
+    createInternalRepository: jest.fn(() => savedObjectsRepoStub),
+  } as any;
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  describe('registerTelemetryUsageCollector', () => {
+    it('should make and register the usage collector', () => {
+      registerTelemetryUsageCollector(usageCollectionMock, savedObjectsMock, mockLogger);
+
+      expect(registerStub).toHaveBeenCalledTimes(1);
+      expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1);
+      expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('app_search');
+      expect(makeUsageCollectorStub.mock.calls[0][0].isReady()).toBe(true);
+    });
+  });
+
+  describe('fetchTelemetryMetrics', () => {
+    it('should return existing saved objects data', async () => {
+      registerTelemetryUsageCollector(usageCollectionMock, savedObjectsMock, mockLogger);
+      const savedObjectsCounts = await makeUsageCollectorStub.mock.calls[0][0].fetch();
+
+      expect(savedObjectsCounts).toEqual({
+        ui_viewed: {
+          setup_guide: 10,
+          engines_overview: 20,
+        },
+        ui_error: {
+          cannot_connect: 3,
+        },
+        ui_clicked: {
+          create_first_engine_button: 40,
+          header_launch_button: 50,
+          engine_table_link: 60,
+        },
+      });
+    });
+
+    it('should return a default telemetry object if no saved data exists', async () => {
+      const emptySavedObjectsMock = {
+        createInternalRepository: () => ({
+          get: () => ({ attributes: null }),
+        }),
+      } as any;
+
+      registerTelemetryUsageCollector(usageCollectionMock, emptySavedObjectsMock, mockLogger);
+      const savedObjectsCounts = await makeUsageCollectorStub.mock.calls[0][0].fetch();
+
+      expect(savedObjectsCounts).toEqual({
+        ui_viewed: {
+          setup_guide: 0,
+          engines_overview: 0,
+        },
+        ui_error: {
+          cannot_connect: 0,
+        },
+        ui_clicked: {
+          create_first_engine_button: 0,
+          header_launch_button: 0,
+          engine_table_link: 0,
+        },
+      });
+    });
+
+    it('should not throw but log a warning if saved objects errors', async () => {
+      const errorSavedObjectsMock = { createInternalRepository: () => ({}) } as any;
+      registerTelemetryUsageCollector(usageCollectionMock, errorSavedObjectsMock, mockLogger);
+
+      // Without log warning (not found)
+      (SavedObjectsErrorHelpers.isNotFoundError as jest.Mock).mockImplementationOnce(() => true);
+      await makeUsageCollectorStub.mock.calls[0][0].fetch();
+
+      expect(mockLogger.warn).not.toHaveBeenCalled();
+
+      // With log warning
+      (SavedObjectsErrorHelpers.isNotFoundError as jest.Mock).mockImplementationOnce(() => false);
+      await makeUsageCollectorStub.mock.calls[0][0].fetch();
+
+      expect(mockLogger.warn).toHaveBeenCalledWith(
+        'Failed to retrieve App Search telemetry data: TypeError: savedObjectsRepository.get is not a function'
+      );
+    });
+  });
+
+  describe('incrementUICounter', () => {
+    it('should increment the saved objects internal repository', async () => {
+      const response = await incrementUICounter({
+        savedObjects: savedObjectsMock,
+        uiAction: 'ui_clicked',
+        metric: 'button',
+      });
+
+      expect(savedObjectsRepoStub.incrementCounter).toHaveBeenCalledWith(
+        'app_search_telemetry',
+        'app_search_telemetry',
+        'ui_clicked.button'
+      );
+      expect(response).toEqual({ success: true });
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts
new file mode 100644
index 0000000000000..a10f96907ad28
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts
@@ -0,0 +1,156 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { get } from 'lodash';
+import {
+  ISavedObjectsRepository,
+  SavedObjectsServiceStart,
+  SavedObjectAttributes,
+  Logger,
+} from 'src/core/server';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+
+// This throws `Error: Cannot find module 'src/core/server'` if I import it via alias ¯\_(ツ)_/¯
+import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
+
+interface ITelemetry {
+  ui_viewed: {
+    setup_guide: number;
+    engines_overview: number;
+  };
+  ui_error: {
+    cannot_connect: number;
+  };
+  ui_clicked: {
+    create_first_engine_button: number;
+    header_launch_button: number;
+    engine_table_link: number;
+  };
+}
+
+export const AS_TELEMETRY_NAME = 'app_search_telemetry';
+
+/**
+ * Register the telemetry collector
+ */
+
+export const registerTelemetryUsageCollector = (
+  usageCollection: UsageCollectionSetup,
+  savedObjects: SavedObjectsServiceStart,
+  log: Logger
+) => {
+  const telemetryUsageCollector = usageCollection.makeUsageCollector<ITelemetry>({
+    type: 'app_search',
+    fetch: async () => fetchTelemetryMetrics(savedObjects, log),
+    isReady: () => true,
+    schema: {
+      ui_viewed: {
+        setup_guide: { type: 'long' },
+        engines_overview: { type: 'long' },
+      },
+      ui_error: {
+        cannot_connect: { type: 'long' },
+      },
+      ui_clicked: {
+        create_first_engine_button: { type: 'long' },
+        header_launch_button: { type: 'long' },
+        engine_table_link: { type: 'long' },
+      },
+    },
+  });
+  usageCollection.registerCollector(telemetryUsageCollector);
+};
+
+/**
+ * Fetch the aggregated telemetry metrics from our saved objects
+ */
+
+const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log: Logger) => {
+  const savedObjectsRepository = savedObjects.createInternalRepository();
+  const savedObjectAttributes = (await getSavedObjectAttributesFromRepo(
+    savedObjectsRepository,
+    log
+  )) as SavedObjectAttributes;
+
+  const defaultTelemetrySavedObject: ITelemetry = {
+    ui_viewed: {
+      setup_guide: 0,
+      engines_overview: 0,
+    },
+    ui_error: {
+      cannot_connect: 0,
+    },
+    ui_clicked: {
+      create_first_engine_button: 0,
+      header_launch_button: 0,
+      engine_table_link: 0,
+    },
+  };
+
+  // If we don't have an existing/saved telemetry object, return the default
+  if (!savedObjectAttributes) {
+    return defaultTelemetrySavedObject;
+  }
+
+  return {
+    ui_viewed: {
+      setup_guide: get(savedObjectAttributes, 'ui_viewed.setup_guide', 0),
+      engines_overview: get(savedObjectAttributes, 'ui_viewed.engines_overview', 0),
+    },
+    ui_error: {
+      cannot_connect: get(savedObjectAttributes, 'ui_error.cannot_connect', 0),
+    },
+    ui_clicked: {
+      create_first_engine_button: get(
+        savedObjectAttributes,
+        'ui_clicked.create_first_engine_button',
+        0
+      ),
+      header_launch_button: get(savedObjectAttributes, 'ui_clicked.header_launch_button', 0),
+      engine_table_link: get(savedObjectAttributes, 'ui_clicked.engine_table_link', 0),
+    },
+  } as ITelemetry;
+};
+
+/**
+ * Helper function - fetches saved objects attributes
+ */
+
+const getSavedObjectAttributesFromRepo = async (
+  savedObjectsRepository: ISavedObjectsRepository,
+  log: Logger
+) => {
+  try {
+    return (await savedObjectsRepository.get(AS_TELEMETRY_NAME, AS_TELEMETRY_NAME)).attributes;
+  } catch (e) {
+    if (!SavedObjectsErrorHelpers.isNotFoundError(e)) {
+      log.warn(`Failed to retrieve App Search telemetry data: ${e}`);
+    }
+    return null;
+  }
+};
+
+/**
+ * Set saved objection attributes - used by telemetry route
+ */
+
+interface IIncrementUICounter {
+  savedObjects: SavedObjectsServiceStart;
+  uiAction: string;
+  metric: string;
+}
+
+export async function incrementUICounter({ savedObjects, uiAction, metric }: IIncrementUICounter) {
+  const internalRepository = savedObjects.createInternalRepository();
+
+  await internalRepository.incrementCounter(
+    AS_TELEMETRY_NAME,
+    AS_TELEMETRY_NAME,
+    `${uiAction}.${metric}` // e.g., ui_viewed.setup_guide
+  );
+
+  return { success: true };
+}
diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts
new file mode 100644
index 0000000000000..1e4159124ed94
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/index.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server';
+import { schema, TypeOf } from '@kbn/config-schema';
+import { EnterpriseSearchPlugin } from './plugin';
+
+export const plugin = (initializerContext: PluginInitializerContext) => {
+  return new EnterpriseSearchPlugin(initializerContext);
+};
+
+export const configSchema = schema.object({
+  host: schema.maybe(schema.string()),
+  enabled: schema.boolean({ defaultValue: true }),
+  accessCheckTimeout: schema.number({ defaultValue: 5000 }),
+  accessCheckTimeoutWarning: schema.number({ defaultValue: 300 }),
+});
+
+export type ConfigType = TypeOf<typeof configSchema>;
+
+export const config: PluginConfigDescriptor<ConfigType> = {
+  schema: configSchema,
+  exposeToBrowser: {
+    host: true,
+  },
+};
diff --git a/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts b/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts
new file mode 100644
index 0000000000000..11d4a387b533f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('./enterprise_search_config_api', () => ({
+  callEnterpriseSearchConfigAPI: jest.fn(),
+}));
+import { callEnterpriseSearchConfigAPI } from './enterprise_search_config_api';
+
+import { checkAccess } from './check_access';
+
+describe('checkAccess', () => {
+  const mockSecurity = {
+    authz: {
+      mode: {
+        useRbacForRequest: () => true,
+      },
+      checkPrivilegesWithRequest: () => ({
+        globally: () => ({
+          hasAllRequested: false,
+        }),
+      }),
+      actions: {
+        ui: {
+          get: () => null,
+        },
+      },
+    },
+  };
+  const mockDependencies = {
+    request: {},
+    config: { host: 'http://localhost:3002' },
+    security: mockSecurity,
+  } as any;
+
+  describe('when security is disabled', () => {
+    it('should allow all access', async () => {
+      const security = undefined;
+      expect(await checkAccess({ ...mockDependencies, security })).toEqual({
+        hasAppSearchAccess: true,
+        hasWorkplaceSearchAccess: true,
+      });
+    });
+  });
+
+  describe('when the user is a superuser', () => {
+    it('should allow all access', async () => {
+      const security = {
+        ...mockSecurity,
+        authz: {
+          mode: { useRbacForRequest: () => true },
+          checkPrivilegesWithRequest: () => ({
+            globally: () => ({
+              hasAllRequested: true,
+            }),
+          }),
+          actions: { ui: { get: () => {} } },
+        },
+      };
+      expect(await checkAccess({ ...mockDependencies, security })).toEqual({
+        hasAppSearchAccess: true,
+        hasWorkplaceSearchAccess: true,
+      });
+    });
+
+    it('falls back to assuming a non-superuser role if auth credentials are missing', async () => {
+      const security = {
+        authz: {
+          ...mockSecurity.authz,
+          checkPrivilegesWithRequest: () => ({
+            globally: () => Promise.reject({ statusCode: 403 }),
+          }),
+        },
+      };
+      expect(await checkAccess({ ...mockDependencies, security })).toEqual({
+        hasAppSearchAccess: false,
+        hasWorkplaceSearchAccess: false,
+      });
+    });
+
+    it('throws other authz errors', async () => {
+      const security = {
+        authz: {
+          ...mockSecurity.authz,
+          checkPrivilegesWithRequest: undefined,
+        },
+      };
+      await expect(checkAccess({ ...mockDependencies, security })).rejects.toThrow();
+    });
+  });
+
+  describe('when the user is a non-superuser', () => {
+    describe('when enterpriseSearch.host is not set in kibana.yml', () => {
+      it('should deny all access', async () => {
+        const config = { host: undefined };
+        expect(await checkAccess({ ...mockDependencies, config })).toEqual({
+          hasAppSearchAccess: false,
+          hasWorkplaceSearchAccess: false,
+        });
+      });
+    });
+
+    describe('when enterpriseSearch.host is set in kibana.yml', () => {
+      it('should make a http call and return the access response', async () => {
+        (callEnterpriseSearchConfigAPI as jest.Mock).mockImplementationOnce(() => ({
+          access: {
+            hasAppSearchAccess: false,
+            hasWorkplaceSearchAccess: true,
+          },
+        }));
+        expect(await checkAccess(mockDependencies)).toEqual({
+          hasAppSearchAccess: false,
+          hasWorkplaceSearchAccess: true,
+        });
+      });
+
+      it('falls back to no access if no http response', async () => {
+        (callEnterpriseSearchConfigAPI as jest.Mock).mockImplementationOnce(() => ({}));
+        expect(await checkAccess(mockDependencies)).toEqual({
+          hasAppSearchAccess: false,
+          hasWorkplaceSearchAccess: false,
+        });
+      });
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/server/lib/check_access.ts b/x-pack/plugins/enterprise_search/server/lib/check_access.ts
new file mode 100644
index 0000000000000..0239cb6422d03
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/check_access.ts
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaRequest, Logger } from 'src/core/server';
+import { SecurityPluginSetup } from '../../../security/server';
+import { ConfigType } from '../';
+
+import { callEnterpriseSearchConfigAPI } from './enterprise_search_config_api';
+
+interface ICheckAccess {
+  request: KibanaRequest;
+  security?: SecurityPluginSetup;
+  config: ConfigType;
+  log: Logger;
+}
+export interface IAccess {
+  hasAppSearchAccess: boolean;
+  hasWorkplaceSearchAccess: boolean;
+}
+
+const ALLOW_ALL_PLUGINS = {
+  hasAppSearchAccess: true,
+  hasWorkplaceSearchAccess: true,
+};
+const DENY_ALL_PLUGINS = {
+  hasAppSearchAccess: false,
+  hasWorkplaceSearchAccess: false,
+};
+
+/**
+ * Determines whether the user has access to our Enterprise Search products
+ * via HTTP call. If not, we hide the corresponding plugin links from the
+ * nav and catalogue in `plugin.ts`, which disables plugin access
+ */
+export const checkAccess = async ({
+  config,
+  security,
+  request,
+  log,
+}: ICheckAccess): Promise<IAccess> => {
+  // If security has been disabled, always show the plugin
+  if (!security?.authz.mode.useRbacForRequest(request)) {
+    return ALLOW_ALL_PLUGINS;
+  }
+
+  // If the user is a "superuser" or has the base Kibana all privilege globally, always show the plugin
+  const isSuperUser = async (): Promise<boolean> => {
+    try {
+      const { hasAllRequested } = await security.authz
+        .checkPrivilegesWithRequest(request)
+        .globally(security.authz.actions.ui.get('enterpriseSearch', 'all'));
+      return hasAllRequested;
+    } catch (err) {
+      if (err.statusCode === 401 || err.statusCode === 403) {
+        return false;
+      }
+      throw err;
+    }
+  };
+  if (await isSuperUser()) {
+    return ALLOW_ALL_PLUGINS;
+  }
+
+  // Hide the plugin when enterpriseSearch.host is not defined in kibana.yml
+  if (!config.host) {
+    return DENY_ALL_PLUGINS;
+  }
+
+  // When enterpriseSearch.host is defined in kibana.yml,
+  // make a HTTP call which returns product access
+  const { access } = (await callEnterpriseSearchConfigAPI({ request, config, log })) || {};
+  return access || DENY_ALL_PLUGINS;
+};
diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts
new file mode 100644
index 0000000000000..cf35a458b4825
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts
@@ -0,0 +1,111 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+jest.mock('node-fetch');
+const fetchMock = require('node-fetch') as jest.Mock;
+const { Response } = jest.requireActual('node-fetch');
+
+import { loggingSystemMock } from 'src/core/server/mocks';
+
+import { callEnterpriseSearchConfigAPI } from './enterprise_search_config_api';
+
+describe('callEnterpriseSearchConfigAPI', () => {
+  const mockConfig = {
+    host: 'http://localhost:3002',
+    accessCheckTimeout: 200,
+    accessCheckTimeoutWarning: 100,
+  };
+  const mockRequest = {
+    url: { path: '/app/kibana' },
+    headers: { authorization: '==someAuth' },
+  };
+  const mockDependencies = {
+    config: mockConfig,
+    request: mockRequest,
+    log: loggingSystemMock.create().get(),
+  } as any;
+
+  const mockResponse = {
+    version: {
+      number: '1.0.0',
+    },
+    settings: {
+      external_url: 'http://some.vanity.url/',
+    },
+    access: {
+      user: 'someuser',
+      products: {
+        app_search: true,
+        workplace_search: false,
+      },
+    },
+  };
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('calls the config API endpoint', async () => {
+    fetchMock.mockImplementationOnce((url: string) => {
+      expect(url).toEqual('http://localhost:3002/api/ent/v1/internal/client_config');
+      return Promise.resolve(new Response(JSON.stringify(mockResponse)));
+    });
+
+    expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({
+      publicUrl: 'http://some.vanity.url/',
+      access: {
+        hasAppSearchAccess: true,
+        hasWorkplaceSearchAccess: false,
+      },
+    });
+  });
+
+  it('returns early if config.host is not set', async () => {
+    const config = { host: '' };
+
+    expect(await callEnterpriseSearchConfigAPI({ ...mockDependencies, config })).toEqual({});
+    expect(fetchMock).not.toHaveBeenCalled();
+  });
+
+  it('handles server errors', async () => {
+    fetchMock.mockImplementationOnce(() => {
+      return Promise.reject('500');
+    });
+    expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({});
+    expect(mockDependencies.log.error).toHaveBeenCalledWith(
+      'Could not perform access check to Enterprise Search: 500'
+    );
+
+    fetchMock.mockImplementationOnce(() => {
+      return Promise.resolve('Bad Data');
+    });
+    expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({});
+    expect(mockDependencies.log.error).toHaveBeenCalledWith(
+      'Could not perform access check to Enterprise Search: TypeError: response.json is not a function'
+    );
+  });
+
+  it('handles timeouts', async () => {
+    jest.useFakeTimers();
+
+    // Warning
+    callEnterpriseSearchConfigAPI(mockDependencies);
+    jest.advanceTimersByTime(150);
+    expect(mockDependencies.log.warn).toHaveBeenCalledWith(
+      'Enterprise Search access check took over 100ms. Please ensure your Enterprise Search server is respondingly normally and not adversely impacting Kibana load speeds.'
+    );
+
+    // Timeout
+    fetchMock.mockImplementationOnce(async () => {
+      jest.advanceTimersByTime(250);
+      return Promise.reject({ name: 'AbortError' });
+    });
+    expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({});
+    expect(mockDependencies.log.warn).toHaveBeenCalledWith(
+      "Exceeded 200ms timeout while checking http://localhost:3002. Please consider increasing your enterpriseSearch.accessCheckTimeout value so that users aren't prevented from accessing Enterprise Search plugins due to slow responses."
+    );
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts
new file mode 100644
index 0000000000000..7a6d1eac1b454
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import AbortController from 'abort-controller';
+import fetch from 'node-fetch';
+
+import { KibanaRequest, Logger } from 'src/core/server';
+import { ConfigType } from '../';
+import { IAccess } from './check_access';
+
+interface IParams {
+  request: KibanaRequest;
+  config: ConfigType;
+  log: Logger;
+}
+interface IReturn {
+  publicUrl?: string;
+  access?: IAccess;
+}
+
+/**
+ * Calls an internal Enterprise Search API endpoint which returns
+ * useful various settings (e.g. product access, external URL)
+ * needed by the Kibana plugin at the setup stage
+ */
+const ENDPOINT = '/api/ent/v1/internal/client_config';
+
+export const callEnterpriseSearchConfigAPI = async ({
+  config,
+  log,
+  request,
+}: IParams): Promise<IReturn> => {
+  if (!config.host) return {};
+
+  const TIMEOUT_WARNING = `Enterprise Search access check took over ${config.accessCheckTimeoutWarning}ms. Please ensure your Enterprise Search server is respondingly normally and not adversely impacting Kibana load speeds.`;
+  const TIMEOUT_MESSAGE = `Exceeded ${config.accessCheckTimeout}ms timeout while checking ${config.host}. Please consider increasing your enterpriseSearch.accessCheckTimeout value so that users aren't prevented from accessing Enterprise Search plugins due to slow responses.`;
+  const CONNECTION_ERROR = 'Could not perform access check to Enterprise Search';
+
+  const warningTimeout = setTimeout(() => {
+    log.warn(TIMEOUT_WARNING);
+  }, config.accessCheckTimeoutWarning);
+
+  const controller = new AbortController();
+  const timeout = setTimeout(() => {
+    controller.abort();
+  }, config.accessCheckTimeout);
+
+  try {
+    const enterpriseSearchUrl = encodeURI(`${config.host}${ENDPOINT}`);
+    const response = await fetch(enterpriseSearchUrl, {
+      headers: { Authorization: request.headers.authorization as string },
+      signal: controller.signal,
+    });
+    const data = await response.json();
+
+    return {
+      publicUrl: data?.settings?.external_url,
+      access: {
+        hasAppSearchAccess: !!data?.access?.products?.app_search,
+        hasWorkplaceSearchAccess: !!data?.access?.products?.workplace_search,
+      },
+    };
+  } catch (err) {
+    if (err.name === 'AbortError') {
+      log.warn(TIMEOUT_MESSAGE);
+    } else {
+      log.error(`${CONNECTION_ERROR}: ${err.toString()}`);
+      if (err instanceof Error) log.debug(err.stack as string);
+    }
+    return {};
+  } finally {
+    clearTimeout(warningTimeout);
+    clearTimeout(timeout);
+  }
+};
diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts
new file mode 100644
index 0000000000000..70be8600862e9
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/plugin.ts
@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Observable } from 'rxjs';
+import { first } from 'rxjs/operators';
+import {
+  Plugin,
+  PluginInitializerContext,
+  CoreSetup,
+  Logger,
+  SavedObjectsServiceStart,
+  IRouter,
+  KibanaRequest,
+} from 'src/core/server';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { SecurityPluginSetup } from '../../security/server';
+import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
+
+import { ConfigType } from './';
+import { checkAccess } from './lib/check_access';
+import { registerPublicUrlRoute } from './routes/enterprise_search/public_url';
+import { registerEnginesRoute } from './routes/app_search/engines';
+import { registerTelemetryRoute } from './routes/app_search/telemetry';
+import { registerTelemetryUsageCollector } from './collectors/app_search/telemetry';
+import { appSearchTelemetryType } from './saved_objects/app_search/telemetry';
+
+export interface PluginsSetup {
+  usageCollection?: UsageCollectionSetup;
+  security?: SecurityPluginSetup;
+  features: FeaturesPluginSetup;
+}
+
+export interface IRouteDependencies {
+  router: IRouter;
+  config: ConfigType;
+  log: Logger;
+  getSavedObjectsService?(): SavedObjectsServiceStart;
+}
+
+export class EnterpriseSearchPlugin implements Plugin {
+  private config: Observable<ConfigType>;
+  private logger: Logger;
+
+  constructor(initializerContext: PluginInitializerContext) {
+    this.config = initializerContext.config.create<ConfigType>();
+    this.logger = initializerContext.logger.get();
+  }
+
+  public async setup(
+    { capabilities, http, savedObjects, getStartServices }: CoreSetup,
+    { usageCollection, security, features }: PluginsSetup
+  ) {
+    const config = await this.config.pipe(first()).toPromise();
+
+    /**
+     * Register space/feature control
+     */
+    features.registerFeature({
+      id: 'enterpriseSearch',
+      name: 'Enterprise Search',
+      order: 0,
+      icon: 'logoEnterpriseSearch',
+      navLinkId: 'appSearch', // TODO - remove this once functional tests no longer rely on navLinkId
+      app: ['kibana', 'appSearch'], // TODO: 'enterpriseSearch', 'workplaceSearch'
+      catalogue: ['appSearch'], // TODO: 'enterpriseSearch', 'workplaceSearch'
+      privileges: null,
+    });
+
+    /**
+     * Register user access to the Enterprise Search plugins
+     */
+    capabilities.registerSwitcher(async (request: KibanaRequest) => {
+      const dependencies = { config, security, request, log: this.logger };
+
+      const { hasAppSearchAccess } = await checkAccess(dependencies);
+      // TODO: hasWorkplaceSearchAccess
+
+      return {
+        navLinks: {
+          appSearch: hasAppSearchAccess,
+        },
+        catalogue: {
+          appSearch: hasAppSearchAccess,
+        },
+      };
+    });
+
+    /**
+     * Register routes
+     */
+    const router = http.createRouter();
+    const dependencies = { router, config, log: this.logger };
+
+    registerPublicUrlRoute(dependencies);
+    registerEnginesRoute(dependencies);
+
+    /**
+     * Bootstrap the routes, saved objects, and collector for telemetry
+     */
+    savedObjects.registerType(appSearchTelemetryType);
+    let savedObjectsStarted: SavedObjectsServiceStart;
+
+    getStartServices().then(([coreStart]) => {
+      savedObjectsStarted = coreStart.savedObjects;
+      if (usageCollection) {
+        registerTelemetryUsageCollector(usageCollection, savedObjectsStarted, this.logger);
+      }
+    });
+    registerTelemetryRoute({
+      ...dependencies,
+      getSavedObjectsService: () => savedObjectsStarted,
+    });
+  }
+
+  public start() {}
+
+  public stop() {}
+}
diff --git a/x-pack/plugins/enterprise_search/server/routes/__mocks__/index.ts b/x-pack/plugins/enterprise_search/server/routes/__mocks__/index.ts
new file mode 100644
index 0000000000000..3cca5e21ce9c3
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/__mocks__/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { MockRouter } from './router.mock';
+export { mockConfig, mockLogger, mockDependencies } from './routerDependencies.mock';
diff --git a/x-pack/plugins/enterprise_search/server/routes/__mocks__/router.mock.ts b/x-pack/plugins/enterprise_search/server/routes/__mocks__/router.mock.ts
new file mode 100644
index 0000000000000..1ca7755979f99
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/__mocks__/router.mock.ts
@@ -0,0 +1,102 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { httpServiceMock, httpServerMock } from 'src/core/server/mocks';
+import {
+  IRouter,
+  KibanaRequest,
+  RequestHandlerContext,
+  RouteValidatorConfig,
+} from 'src/core/server';
+
+/**
+ * Test helper that mocks Kibana's router and DRYs out various helper (callRoute, schema validation)
+ */
+
+type methodType = 'get' | 'post' | 'put' | 'patch' | 'delete';
+type payloadType = 'params' | 'query' | 'body';
+
+interface IMockRouterProps {
+  method: methodType;
+  payload?: payloadType;
+}
+interface IMockRouterRequest {
+  body?: object;
+  query?: object;
+  params?: object;
+}
+type TMockRouterRequest = KibanaRequest | IMockRouterRequest;
+
+export class MockRouter {
+  public router!: jest.Mocked<IRouter>;
+  public method: methodType;
+  public payload?: payloadType;
+  public response = httpServerMock.createResponseFactory();
+
+  constructor({ method, payload }: IMockRouterProps) {
+    this.createRouter();
+    this.method = method;
+    this.payload = payload;
+  }
+
+  public createRouter = () => {
+    this.router = httpServiceMock.createRouter();
+  };
+
+  public callRoute = async (request: TMockRouterRequest) => {
+    const [, handler] = this.router[this.method].mock.calls[0];
+
+    const context = {} as jest.Mocked<RequestHandlerContext>;
+    await handler(context, httpServerMock.createKibanaRequest(request as any), this.response);
+  };
+
+  /**
+   * Schema validation helpers
+   */
+
+  public validateRoute = (request: TMockRouterRequest) => {
+    if (!this.payload) throw new Error('Cannot validate wihout a payload type specified.');
+
+    const [config] = this.router[this.method].mock.calls[0];
+    const validate = config.validate as RouteValidatorConfig<{}, {}, {}>;
+
+    const payloadValidation = validate[this.payload] as { validate(request: KibanaRequest): void };
+    const payloadRequest = request[this.payload] as KibanaRequest;
+
+    payloadValidation.validate(payloadRequest);
+  };
+
+  public shouldValidate = (request: TMockRouterRequest) => {
+    expect(() => this.validateRoute(request)).not.toThrow();
+  };
+
+  public shouldThrow = (request: TMockRouterRequest) => {
+    expect(() => this.validateRoute(request)).toThrow();
+  };
+}
+
+/**
+ * Example usage:
+ */
+// const mockRouter = new MockRouter({ method: 'get', payload: 'body' });
+//
+// beforeEach(() => {
+//   jest.clearAllMocks();
+//   mockRouter.createRouter();
+//
+//   registerExampleRoute({ router: mockRouter.router, ...dependencies }); // Whatever other dependencies the route needs
+// });
+
+// it('hits the endpoint successfully', async () => {
+//   await mockRouter.callRoute({ body: { foo: 'bar' } });
+//
+//   expect(mockRouter.response.ok).toHaveBeenCalled();
+// });
+
+// it('validates', () => {
+//   const request = { body: { foo: 'bar' } };
+//   mockRouter.shouldValidate(request);
+// });
diff --git a/x-pack/plugins/enterprise_search/server/routes/__mocks__/routerDependencies.mock.ts b/x-pack/plugins/enterprise_search/server/routes/__mocks__/routerDependencies.mock.ts
new file mode 100644
index 0000000000000..9b6fa30271d61
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/__mocks__/routerDependencies.mock.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { loggingSystemMock } from 'src/core/server/mocks';
+import { ConfigType } from '../../';
+
+export const mockLogger = loggingSystemMock.createLogger().get();
+
+export const mockConfig = {
+  enabled: true,
+  host: 'http://localhost:3002',
+  accessCheckTimeout: 5000,
+  accessCheckTimeoutWarning: 300,
+} as ConfigType;
+
+/**
+ * This is useful for tests that don't use either config or log,
+ * but should still pass them in to pass Typescript definitions
+ */
+export const mockDependencies = {
+  // Mock router should be handled on a per-test basis
+  config: mockConfig,
+  log: mockLogger,
+};
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts
new file mode 100644
index 0000000000000..d5b1bc5003456
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts
@@ -0,0 +1,160 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { MockRouter, mockConfig, mockLogger } from '../__mocks__';
+
+import { registerEnginesRoute } from './engines';
+
+jest.mock('node-fetch');
+const fetch = jest.requireActual('node-fetch');
+const { Response } = fetch;
+const fetchMock = require('node-fetch') as jest.Mocked<typeof fetch>;
+
+describe('engine routes', () => {
+  describe('GET /api/app_search/engines', () => {
+    const AUTH_HEADER = 'Basic 123';
+    const mockRequest = {
+      headers: {
+        authorization: AUTH_HEADER,
+      },
+      query: {
+        type: 'indexed',
+        pageIndex: 1,
+      },
+    };
+
+    let mockRouter: MockRouter;
+
+    beforeEach(() => {
+      jest.clearAllMocks();
+      mockRouter = new MockRouter({ method: 'get', payload: 'query' });
+
+      registerEnginesRoute({
+        router: mockRouter.router,
+        log: mockLogger,
+        config: mockConfig,
+      });
+    });
+
+    describe('when the underlying App Search API returns a 200', () => {
+      beforeEach(() => {
+        AppSearchAPI.shouldBeCalledWith(
+          `http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
+          { headers: { Authorization: AUTH_HEADER } }
+        ).andReturn({
+          results: [{ name: 'engine1' }],
+          meta: { page: { total_results: 1 } },
+        });
+      });
+
+      it('should return 200 with a list of engines from the App Search API', async () => {
+        await mockRouter.callRoute(mockRequest);
+
+        expect(mockRouter.response.ok).toHaveBeenCalledWith({
+          body: { results: [{ name: 'engine1' }], meta: { page: { total_results: 1 } } },
+        });
+      });
+    });
+
+    describe('when the App Search URL is invalid', () => {
+      beforeEach(() => {
+        AppSearchAPI.shouldBeCalledWith(
+          `http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
+          { headers: { Authorization: AUTH_HEADER } }
+        ).andReturnError();
+      });
+
+      it('should return 404 with a message', async () => {
+        await mockRouter.callRoute(mockRequest);
+
+        expect(mockRouter.response.notFound).toHaveBeenCalledWith({
+          body: 'cannot-connect',
+        });
+        expect(mockLogger.error).toHaveBeenCalledWith('Cannot connect to App Search: Failed');
+        expect(mockLogger.debug).not.toHaveBeenCalled();
+      });
+    });
+
+    describe('when the App Search API returns invalid data', () => {
+      beforeEach(() => {
+        AppSearchAPI.shouldBeCalledWith(
+          `http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
+          { headers: { Authorization: AUTH_HEADER } }
+        ).andReturnInvalidData();
+      });
+
+      it('should return 404 with a message', async () => {
+        await mockRouter.callRoute(mockRequest);
+
+        expect(mockRouter.response.notFound).toHaveBeenCalledWith({
+          body: 'cannot-connect',
+        });
+        expect(mockLogger.error).toHaveBeenCalledWith(
+          'Cannot connect to App Search: Error: Invalid data received from App Search: {"foo":"bar"}'
+        );
+        expect(mockLogger.debug).toHaveBeenCalled();
+      });
+    });
+
+    describe('validates', () => {
+      it('correctly', () => {
+        const request = { query: { type: 'meta', pageIndex: 5 } };
+        mockRouter.shouldValidate(request);
+      });
+
+      it('wrong pageIndex type', () => {
+        const request = { query: { type: 'indexed', pageIndex: 'indexed' } };
+        mockRouter.shouldThrow(request);
+      });
+
+      it('wrong type string', () => {
+        const request = { query: { type: 'invalid', pageIndex: 1 } };
+        mockRouter.shouldThrow(request);
+      });
+
+      it('missing pageIndex', () => {
+        const request = { query: { type: 'indexed' } };
+        mockRouter.shouldThrow(request);
+      });
+
+      it('missing type', () => {
+        const request = { query: { pageIndex: 1 } };
+        mockRouter.shouldThrow(request);
+      });
+    });
+
+    const AppSearchAPI = {
+      shouldBeCalledWith(expectedUrl: string, expectedParams: object) {
+        return {
+          andReturn(response: object) {
+            fetchMock.mockImplementation((url: string, params: object) => {
+              expect(url).toEqual(expectedUrl);
+              expect(params).toEqual(expectedParams);
+
+              return Promise.resolve(new Response(JSON.stringify(response)));
+            });
+          },
+          andReturnInvalidData() {
+            fetchMock.mockImplementation((url: string, params: object) => {
+              expect(url).toEqual(expectedUrl);
+              expect(params).toEqual(expectedParams);
+
+              return Promise.resolve(new Response(JSON.stringify({ foo: 'bar' })));
+            });
+          },
+          andReturnError() {
+            fetchMock.mockImplementation((url: string, params: object) => {
+              expect(url).toEqual(expectedUrl);
+              expect(params).toEqual(expectedParams);
+
+              return Promise.reject('Failed');
+            });
+          },
+        };
+      },
+    };
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts
new file mode 100644
index 0000000000000..ca83c0e187ddb
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import fetch from 'node-fetch';
+import querystring from 'querystring';
+import { schema } from '@kbn/config-schema';
+
+import { IRouteDependencies } from '../../plugin';
+import { ENGINES_PAGE_SIZE } from '../../../common/constants';
+
+export function registerEnginesRoute({ router, config, log }: IRouteDependencies) {
+  router.get(
+    {
+      path: '/api/app_search/engines',
+      validate: {
+        query: schema.object({
+          type: schema.oneOf([schema.literal('indexed'), schema.literal('meta')]),
+          pageIndex: schema.number(),
+        }),
+      },
+    },
+    async (context, request, response) => {
+      try {
+        const enterpriseSearchUrl = config.host as string;
+        const { type, pageIndex } = request.query;
+
+        const params = querystring.stringify({
+          type,
+          'page[current]': pageIndex,
+          'page[size]': ENGINES_PAGE_SIZE,
+        });
+        const url = `${encodeURI(enterpriseSearchUrl)}/as/engines/collection?${params}`;
+
+        const enginesResponse = await fetch(url, {
+          headers: { Authorization: request.headers.authorization as string },
+        });
+
+        const engines = await enginesResponse.json();
+        const hasValidData =
+          Array.isArray(engines?.results) && typeof engines?.meta?.page?.total_results === 'number';
+
+        if (hasValidData) {
+          return response.ok({ body: engines });
+        } else {
+          // Either a completely incorrect Enterprise Search host URL was configured, or App Search is returning bad data
+          throw new Error(`Invalid data received from App Search: ${JSON.stringify(engines)}`);
+        }
+      } catch (e) {
+        log.error(`Cannot connect to App Search: ${e.toString()}`);
+        if (e instanceof Error) log.debug(e.stack as string);
+
+        return response.notFound({ body: 'cannot-connect' });
+      }
+    }
+  );
+}
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.test.ts
new file mode 100644
index 0000000000000..e2d5fbcec3705
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.test.ts
@@ -0,0 +1,108 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
+import { MockRouter, mockConfig, mockLogger } from '../__mocks__';
+
+import { registerTelemetryRoute } from './telemetry';
+
+jest.mock('../../collectors/app_search/telemetry', () => ({
+  incrementUICounter: jest.fn(),
+}));
+import { incrementUICounter } from '../../collectors/app_search/telemetry';
+
+/**
+ * Since these route callbacks are so thin, these serve simply as integration tests
+ * to ensure they're wired up to the collector functions correctly. Business logic
+ * is tested more thoroughly in the collectors/telemetry tests.
+ */
+describe('App Search Telemetry API', () => {
+  let mockRouter: MockRouter;
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+    mockRouter = new MockRouter({ method: 'put', payload: 'body' });
+
+    registerTelemetryRoute({
+      router: mockRouter.router,
+      getSavedObjectsService: () => savedObjectsServiceMock.createStartContract(),
+      log: mockLogger,
+      config: mockConfig,
+    });
+  });
+
+  describe('PUT /api/app_search/telemetry', () => {
+    it('increments the saved objects counter', async () => {
+      const successResponse = { success: true };
+      (incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => successResponse));
+
+      await mockRouter.callRoute({ body: { action: 'viewed', metric: 'setup_guide' } });
+
+      expect(incrementUICounter).toHaveBeenCalledWith({
+        savedObjects: expect.any(Object),
+        uiAction: 'ui_viewed',
+        metric: 'setup_guide',
+      });
+      expect(mockRouter.response.ok).toHaveBeenCalledWith({ body: successResponse });
+    });
+
+    it('throws an error when incrementing fails', async () => {
+      (incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => Promise.reject('Failed')));
+
+      await mockRouter.callRoute({ body: { action: 'error', metric: 'error' } });
+
+      expect(incrementUICounter).toHaveBeenCalled();
+      expect(mockLogger.error).toHaveBeenCalled();
+      expect(mockRouter.response.internalError).toHaveBeenCalled();
+    });
+
+    it('throws an error if the Saved Objects service is unavailable', async () => {
+      jest.clearAllMocks();
+      registerTelemetryRoute({
+        router: mockRouter.router,
+        getSavedObjectsService: null,
+        log: mockLogger,
+      } as any);
+      await mockRouter.callRoute({});
+
+      expect(incrementUICounter).not.toHaveBeenCalled();
+      expect(mockLogger.error).toHaveBeenCalled();
+      expect(mockRouter.response.internalError).toHaveBeenCalled();
+      expect(loggingSystemMock.collect(mockLogger).error[0][0]).toEqual(
+        expect.stringContaining(
+          'App Search UI telemetry error: Error: Could not find Saved Objects service'
+        )
+      );
+    });
+
+    describe('validates', () => {
+      it('correctly', () => {
+        const request = { body: { action: 'viewed', metric: 'setup_guide' } };
+        mockRouter.shouldValidate(request);
+      });
+
+      it('wrong action string', () => {
+        const request = { body: { action: 'invalid', metric: 'setup_guide' } };
+        mockRouter.shouldThrow(request);
+      });
+
+      it('wrong metric type', () => {
+        const request = { body: { action: 'clicked', metric: true } };
+        mockRouter.shouldThrow(request);
+      });
+
+      it('action is missing', () => {
+        const request = { body: { metric: 'engines_overview' } };
+        mockRouter.shouldThrow(request);
+      });
+
+      it('metric is missing', () => {
+        const request = { body: { action: 'error' } };
+        mockRouter.shouldThrow(request);
+      });
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.ts
new file mode 100644
index 0000000000000..4cc9b64adc092
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/telemetry.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema } from '@kbn/config-schema';
+
+import { IRouteDependencies } from '../../plugin';
+import { incrementUICounter } from '../../collectors/app_search/telemetry';
+
+export function registerTelemetryRoute({
+  router,
+  getSavedObjectsService,
+  log,
+}: IRouteDependencies) {
+  router.put(
+    {
+      path: '/api/app_search/telemetry',
+      validate: {
+        body: schema.object({
+          action: schema.oneOf([
+            schema.literal('viewed'),
+            schema.literal('clicked'),
+            schema.literal('error'),
+          ]),
+          metric: schema.string(),
+        }),
+      },
+    },
+    async (ctx, request, response) => {
+      const { action, metric } = request.body;
+
+      try {
+        if (!getSavedObjectsService) throw new Error('Could not find Saved Objects service');
+
+        return response.ok({
+          body: await incrementUICounter({
+            savedObjects: getSavedObjectsService(),
+            uiAction: `ui_${action}`,
+            metric,
+          }),
+        });
+      } catch (e) {
+        log.error(`App Search UI telemetry error: ${e instanceof Error ? e.stack : e.toString()}`);
+        return response.internalError({ body: 'App Search UI telemetry failed' });
+      }
+    }
+  );
+}
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.test.ts
new file mode 100644
index 0000000000000..846aae3fce56f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.test.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { MockRouter, mockDependencies } from '../__mocks__';
+
+jest.mock('../../lib/enterprise_search_config_api', () => ({
+  callEnterpriseSearchConfigAPI: jest.fn(),
+}));
+import { callEnterpriseSearchConfigAPI } from '../../lib/enterprise_search_config_api';
+
+import { registerPublicUrlRoute } from './public_url';
+
+describe('Enterprise Search Public URL API', () => {
+  let mockRouter: MockRouter;
+
+  beforeEach(() => {
+    mockRouter = new MockRouter({ method: 'get' });
+
+    registerPublicUrlRoute({
+      ...mockDependencies,
+      router: mockRouter.router,
+    });
+  });
+
+  describe('GET /api/enterprise_search/public_url', () => {
+    it('returns a publicUrl', async () => {
+      (callEnterpriseSearchConfigAPI as jest.Mock).mockImplementationOnce(() => {
+        return Promise.resolve({ publicUrl: 'http://some.vanity.url' });
+      });
+
+      await mockRouter.callRoute({});
+
+      expect(mockRouter.response.ok).toHaveBeenCalledWith({
+        body: { publicUrl: 'http://some.vanity.url' },
+        headers: { 'content-type': 'application/json' },
+      });
+    });
+
+    // For the most part, all error logging is handled by callEnterpriseSearchConfigAPI.
+    // This endpoint should mostly just fall back gracefully to an empty string
+    it('falls back to an empty string', async () => {
+      await mockRouter.callRoute({});
+      expect(mockRouter.response.ok).toHaveBeenCalledWith({
+        body: { publicUrl: '' },
+        headers: { 'content-type': 'application/json' },
+      });
+    });
+  });
+});
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.ts
new file mode 100644
index 0000000000000..a9edd4eb10da0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/public_url.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IRouteDependencies } from '../../plugin';
+import { callEnterpriseSearchConfigAPI } from '../../lib/enterprise_search_config_api';
+
+export function registerPublicUrlRoute({ router, config, log }: IRouteDependencies) {
+  router.get(
+    {
+      path: '/api/enterprise_search/public_url',
+      validate: false,
+    },
+    async (context, request, response) => {
+      const { publicUrl = '' } =
+        (await callEnterpriseSearchConfigAPI({ request, config, log })) || {};
+
+      return response.ok({
+        body: { publicUrl },
+        headers: { 'content-type': 'application/json' },
+      });
+    }
+  );
+}
diff --git a/x-pack/plugins/enterprise_search/server/saved_objects/app_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/saved_objects/app_search/telemetry.ts
new file mode 100644
index 0000000000000..32322d494b5e2
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/saved_objects/app_search/telemetry.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+/* istanbul ignore file */
+
+import { SavedObjectsType } from 'src/core/server';
+import { AS_TELEMETRY_NAME } from '../../collectors/app_search/telemetry';
+
+export const appSearchTelemetryType: SavedObjectsType = {
+  name: AS_TELEMETRY_NAME,
+  hidden: false,
+  namespaceType: 'agnostic',
+  mappings: {
+    dynamic: false,
+    properties: {},
+  },
+};
diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts
index 06f064a379fe6..8a499a3eba8fa 100644
--- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts
+++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts
@@ -189,13 +189,15 @@ describe('features', () => {
     group: 'global',
     expectManageSpaces: true,
     expectGetFeatures: true,
+    expectEnterpriseSearch: true,
   },
   {
     group: 'space',
     expectManageSpaces: false,
     expectGetFeatures: false,
+    expectEnterpriseSearch: false,
   },
-].forEach(({ group, expectManageSpaces, expectGetFeatures }) => {
+].forEach(({ group, expectManageSpaces, expectGetFeatures, expectEnterpriseSearch }) => {
   describe(`${group}`, () => {
     test('actions defined in any feature privilege are included in `all`', () => {
       const features: Feature[] = [
@@ -256,6 +258,7 @@ describe('features', () => {
               actions.ui.get('management', 'kibana', 'spaces'),
             ]
           : []),
+        ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []),
         actions.ui.get('catalogue', 'all-catalogue-1'),
         actions.ui.get('catalogue', 'all-catalogue-2'),
         actions.ui.get('management', 'all-management', 'all-management-1'),
@@ -450,6 +453,7 @@ describe('features', () => {
               actions.ui.get('management', 'kibana', 'spaces'),
             ]
           : []),
+        ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []),
       ]);
       expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]);
     });
@@ -514,6 +518,7 @@ describe('features', () => {
               actions.ui.get('management', 'kibana', 'spaces'),
             ]
           : []),
+        ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []),
       ]);
       expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]);
     });
@@ -579,6 +584,7 @@ describe('features', () => {
               actions.ui.get('management', 'kibana', 'spaces'),
             ]
           : []),
+        ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []),
       ]);
       expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]);
     });
@@ -840,6 +846,7 @@ describe('subFeatures', () => {
         actions.space.manage,
         actions.ui.get('spaces', 'manage'),
         actions.ui.get('management', 'kibana', 'spaces'),
+        actions.ui.get('enterpriseSearch', 'all'),
         actions.ui.get('foo', 'foo'),
       ]);
       expect(actual).toHaveProperty('global.read', [
@@ -991,6 +998,7 @@ describe('subFeatures', () => {
         actions.space.manage,
         actions.ui.get('spaces', 'manage'),
         actions.ui.get('management', 'kibana', 'spaces'),
+        actions.ui.get('enterpriseSearch', 'all'),
         actions.savedObject.get('all-sub-feature-type', 'bulk_get'),
         actions.savedObject.get('all-sub-feature-type', 'get'),
         actions.savedObject.get('all-sub-feature-type', 'find'),
@@ -1189,6 +1197,7 @@ describe('subFeatures', () => {
         actions.space.manage,
         actions.ui.get('spaces', 'manage'),
         actions.ui.get('management', 'kibana', 'spaces'),
+        actions.ui.get('enterpriseSearch', 'all'),
       ]);
       expect(actual).toHaveProperty('global.read', [actions.login, actions.version]);
 
@@ -1315,6 +1324,7 @@ describe('subFeatures', () => {
         actions.space.manage,
         actions.ui.get('spaces', 'manage'),
         actions.ui.get('management', 'kibana', 'spaces'),
+        actions.ui.get('enterpriseSearch', 'all'),
         actions.savedObject.get('all-sub-feature-type', 'bulk_get'),
         actions.savedObject.get('all-sub-feature-type', 'get'),
         actions.savedObject.get('all-sub-feature-type', 'find'),
@@ -1477,6 +1487,7 @@ describe('subFeatures', () => {
         actions.space.manage,
         actions.ui.get('spaces', 'manage'),
         actions.ui.get('management', 'kibana', 'spaces'),
+        actions.ui.get('enterpriseSearch', 'all'),
       ]);
       expect(actual).toHaveProperty('global.read', [actions.login, actions.version]);
 
@@ -1592,6 +1603,7 @@ describe('subFeatures', () => {
         actions.space.manage,
         actions.ui.get('spaces', 'manage'),
         actions.ui.get('management', 'kibana', 'spaces'),
+        actions.ui.get('enterpriseSearch', 'all'),
         actions.savedObject.get('all-sub-feature-type', 'bulk_get'),
         actions.savedObject.get('all-sub-feature-type', 'get'),
         actions.savedObject.get('all-sub-feature-type', 'find'),
diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.ts
index 5a15290a7f1a2..f9ee5fc750127 100644
--- a/x-pack/plugins/security/server/authorization/privileges/privileges.ts
+++ b/x-pack/plugins/security/server/authorization/privileges/privileges.ts
@@ -101,6 +101,7 @@ export function privilegesFactory(
             actions.space.manage,
             actions.ui.get('spaces', 'manage'),
             actions.ui.get('management', 'kibana', 'spaces'),
+            actions.ui.get('enterpriseSearch', 'all'),
             ...allActions,
           ],
           read: [actions.login, actions.version, ...readActions],
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index 13d7c62316040..1ea16a2a9940c 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -7,6 +7,40 @@
         }
       }
     },
+    "app_search": {
+      "properties": {
+        "ui_viewed": {
+          "properties": {
+            "setup_guide": {
+              "type": "long"
+            },
+            "engines_overview": {
+              "type": "long"
+            }
+          }
+        },
+        "ui_error": {
+          "properties": {
+            "cannot_connect": {
+              "type": "long"
+            }
+          }
+        },
+        "ui_clicked": {
+          "properties": {
+            "create_first_engine_button": {
+              "type": "long"
+            },
+            "header_launch_button": {
+              "type": "long"
+            },
+            "engine_table_link": {
+              "type": "long"
+            }
+          }
+        }
+      }
+    },
     "fileUploadTelemetry": {
       "properties": {
         "filesUploadedTotalCount": {
diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js
index 29be6d826c1bc..ee8af9e040401 100644
--- a/x-pack/scripts/functional_tests.js
+++ b/x-pack/scripts/functional_tests.js
@@ -53,6 +53,7 @@ const onlyNotInCoverageTests = [
   require.resolve('../test/reporting_api_integration/config.js'),
   require.resolve('../test/functional_embedded/config.ts'),
   require.resolve('../test/ingest_manager_api_integration/config.ts'),
+  require.resolve('../test/functional_enterprise_search/without_host_configured.config.ts'),
 ];
 
 require('@kbn/plugin-helpers').babelRegister();
diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts
index 11fb9b2de7199..df6eca795f801 100644
--- a/x-pack/test/api_integration/apis/features/features/features.ts
+++ b/x-pack/test/api_integration/apis/features/features/features.ts
@@ -97,6 +97,7 @@ export default function ({ getService }: FtrProviderContext) {
             'visualize',
             'dashboard',
             'dev_tools',
+            'enterpriseSearch',
             'advancedSettings',
             'indexPatterns',
             'timelion',
diff --git a/x-pack/test/functional_enterprise_search/README.md b/x-pack/test/functional_enterprise_search/README.md
new file mode 100644
index 0000000000000..63d13cbac7020
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/README.md
@@ -0,0 +1,41 @@
+# Enterprise Search Functional E2E Tests
+
+## Running these tests
+
+Follow the [Functional Test Runner instructions](https://www.elastic.co/guide/en/kibana/current/development-functional-tests.html#_running_functional_tests).
+
+There are two suites available to run, a suite that requires a Kibana instance without an `enterpriseSearch.host`
+configured, and one that does. The later also [requires a running Enterprise Search instance](#enterprise-search-requirement), and a Private API key
+from that instance set in an Environment variable.
+
+Ex.
+
+```sh
+# Run specs from the x-pack directory
+cd x-pack
+
+# Run tests that do not require enterpriseSearch.host variable
+node scripts/functional_tests --config test/functional_enterprise_search/without_host_configured.config.ts
+
+# Run tests that require enterpriseSearch.host variable
+APP_SEARCH_API_KEY=[use private key from local App Search instance here] node scripts/functional_tests --config test/functional_enterprise_search/with_host_configured.config.ts
+```
+
+## Enterprise Search Requirement
+
+The `with_host_configured` tests will not currently start an instance of App Search automatically. As such, they are not run as part of CI and are most useful for local regression testing.
+
+The easiest way to start Enterprise Search for these tests is to check out the `ent-search` project
+and use the following script.
+
+```sh
+cd script/stack_scripts
+/start-with-license-and-expiration.sh platinum 500000
+```
+
+Requirements for Enterprise Search:
+
+- Running on port 3002 against a separate Elasticsearch cluster.
+- Elasticsearch must have a platinum or greater level license (or trial).
+- Must have Standard or Native Auth configured with an `enterprise_search` user with password `changeme`.
+- There should be NO existing Engines or Meta Engines.
diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/app_search/engines.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/app_search/engines.ts
new file mode 100644
index 0000000000000..e4ebd61c0692a
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/app_search/engines.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { EsArchiver } from 'src/es_archiver';
+import { AppSearchService, IEngine } from '../../../../services/app_search_service';
+import { Browser } from '../../../../../../../test/functional/services/common';
+import { FtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function enterpriseSearchSetupEnginesTests({
+  getService,
+  getPageObjects,
+}: FtrProviderContext) {
+  const esArchiver = getService('esArchiver') as EsArchiver;
+  const browser = getService('browser') as Browser;
+  const retry = getService('retry');
+  const appSearch = getService('appSearch') as AppSearchService;
+
+  const PageObjects = getPageObjects(['appSearch', 'security']);
+
+  describe('Engines Overview', function () {
+    let engine1: IEngine;
+    let engine2: IEngine;
+    let metaEngine: IEngine;
+
+    before(async () => {
+      await esArchiver.load('empty_kibana');
+      engine1 = await appSearch.createEngine();
+      engine2 = await appSearch.createEngine();
+      metaEngine = await appSearch.createMetaEngine([engine1.name, engine2.name]);
+    });
+
+    after(async () => {
+      await esArchiver.unload('empty_kibana');
+      appSearch.destroyEngine(engine1.name);
+      appSearch.destroyEngine(engine2.name);
+      appSearch.destroyEngine(metaEngine.name);
+    });
+
+    describe('when an enterpriseSearch.host is configured', () => {
+      it('navigating to the enterprise_search plugin will redirect a user to the App Search Engines Overview page', async () => {
+        await PageObjects.security.forceLogout();
+        const { user, password } = appSearch.getEnterpriseSearchUser();
+        await PageObjects.security.login(user, password, {
+          expectSpaceSelector: false,
+        });
+
+        await PageObjects.appSearch.navigateToPage();
+        await retry.try(async function () {
+          const currentUrl = await browser.getCurrentUrl();
+          expect(currentUrl).to.contain('/app_search');
+        });
+      });
+
+      it('lists engines', async () => {
+        const engineLinks = await PageObjects.appSearch.getEngineLinks();
+        const engineLinksText = await Promise.all(engineLinks.map((l) => l.getVisibleText()));
+
+        expect(engineLinksText.includes(engine1.name)).to.equal(true);
+        expect(engineLinksText.includes(engine2.name)).to.equal(true);
+      });
+
+      it('lists meta engines', async () => {
+        const metaEngineLinks = await PageObjects.appSearch.getMetaEngineLinks();
+        const metaEngineLinksText = await Promise.all(
+          metaEngineLinks.map((l) => l.getVisibleText())
+        );
+        expect(metaEngineLinksText.includes(metaEngine.name)).to.equal(true);
+      });
+    });
+  });
+}
diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/index.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/index.ts
new file mode 100644
index 0000000000000..ac4984e0db019
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/apps/enterprise_search/with_host_configured/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ loadTestFile }: FtrProviderContext) {
+  describe('Enterprise Search', function () {
+    loadTestFile(require.resolve('./app_search/engines'));
+  });
+}
diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts
new file mode 100644
index 0000000000000..1d478c6baf29c
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/app_search/setup_guide.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../../../ftr_provider_context';
+
+export default function enterpriseSearchSetupGuideTests({
+  getService,
+  getPageObjects,
+}: FtrProviderContext) {
+  const esArchiver = getService('esArchiver');
+  const browser = getService('browser');
+  const retry = getService('retry');
+
+  const PageObjects = getPageObjects(['appSearch']);
+
+  describe('Setup Guide', function () {
+    before(async () => await esArchiver.load('empty_kibana'));
+    after(async () => {
+      await esArchiver.unload('empty_kibana');
+    });
+
+    describe('when no enterpriseSearch.host is configured', () => {
+      it('navigating to the enterprise_search plugin will redirect a user to the setup guide', async () => {
+        await PageObjects.appSearch.navigateToPage();
+        await retry.try(async function () {
+          const currentUrl = await browser.getCurrentUrl();
+          expect(currentUrl).to.contain('/app_search/setup_guide');
+        });
+      });
+    });
+  });
+}
diff --git a/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts
new file mode 100644
index 0000000000000..31a92e752fcf4
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/apps/enterprise_search/without_host_configured/index.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+export default function ({ loadTestFile }: FtrProviderContext) {
+  describe('Enterprise Search', function () {
+    this.tags('ciGroup10');
+
+    loadTestFile(require.resolve('./app_search/setup_guide'));
+  });
+}
diff --git a/x-pack/test/functional_enterprise_search/base_config.ts b/x-pack/test/functional_enterprise_search/base_config.ts
new file mode 100644
index 0000000000000..f737b6cd4b5f4
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/base_config.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+import { pageObjects } from './page_objects';
+import { services } from './services';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+  const xPackFunctionalConfig = await readConfigFile(require.resolve('../functional/config'));
+
+  return {
+    // default to the xpack functional config
+    ...xPackFunctionalConfig.getAll(),
+    services,
+    pageObjects,
+  };
+}
diff --git a/x-pack/test/functional_enterprise_search/ftr_provider_context.d.ts b/x-pack/test/functional_enterprise_search/ftr_provider_context.d.ts
new file mode 100644
index 0000000000000..bb257cdcbfe1b
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/ftr_provider_context.d.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
+
+import { pageObjects } from './page_objects';
+import { services } from './services';
+
+export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;
diff --git a/x-pack/test/functional_enterprise_search/page_objects/app_search.ts b/x-pack/test/functional_enterprise_search/page_objects/app_search.ts
new file mode 100644
index 0000000000000..d845a1935a149
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/page_objects/app_search.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../ftr_provider_context';
+import { TestSubjects } from '../../../../test/functional/services/common';
+import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+
+export function AppSearchPageProvider({ getService, getPageObjects }: FtrProviderContext) {
+  const PageObjects = getPageObjects(['common']);
+  const testSubjects = getService('testSubjects') as TestSubjects;
+
+  return {
+    async navigateToPage(): Promise<void> {
+      return await PageObjects.common.navigateToApp('enterprise_search/app_search');
+    },
+
+    async getEngineLinks(): Promise<WebElementWrapper[]> {
+      const engines = await testSubjects.find('appSearchEngines');
+      return await testSubjects.findAllDescendant('engineNameLink', engines);
+    },
+
+    async getMetaEngineLinks(): Promise<WebElementWrapper[]> {
+      const metaEngines = await testSubjects.find('appSearchMetaEngines');
+      return await testSubjects.findAllDescendant('engineNameLink', metaEngines);
+    },
+  };
+}
diff --git a/x-pack/test/functional_enterprise_search/page_objects/index.ts b/x-pack/test/functional_enterprise_search/page_objects/index.ts
new file mode 100644
index 0000000000000..009fb26482419
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/page_objects/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { pageObjects as basePageObjects } from '../../functional/page_objects';
+import { AppSearchPageProvider } from './app_search';
+
+export const pageObjects = {
+  ...basePageObjects,
+  appSearch: AppSearchPageProvider,
+};
diff --git a/x-pack/test/functional_enterprise_search/services/app_search_client.ts b/x-pack/test/functional_enterprise_search/services/app_search_client.ts
new file mode 100644
index 0000000000000..fbd15b83f97ea
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/services/app_search_client.ts
@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import http from 'http';
+
+/**
+ * A simple request client for making API calls to the App Search API
+ */
+const makeRequest = <T>(method: string, path: string, body?: object): Promise<T> => {
+  return new Promise(function (resolve, reject) {
+    const APP_SEARCH_API_KEY = process.env.APP_SEARCH_API_KEY;
+
+    if (!APP_SEARCH_API_KEY) {
+      throw new Error('Please provide a valid APP_SEARCH_API_KEY. See README for more details.');
+    }
+
+    let postData;
+
+    if (body) {
+      postData = JSON.stringify(body);
+    }
+
+    const req = http.request(
+      {
+        method,
+        hostname: 'localhost',
+        port: 3002,
+        path,
+        agent: false, // Create a new agent just for this one request
+        headers: {
+          Authorization: `Bearer ${APP_SEARCH_API_KEY}`,
+          'Content-Type': 'application/json',
+          ...(!!postData && { 'Content-Length': Buffer.byteLength(postData) }),
+        },
+      },
+      (res) => {
+        const bodyChunks: Uint8Array[] = [];
+        res.on('data', function (chunk) {
+          bodyChunks.push(chunk);
+        });
+
+        res.on('end', function () {
+          let responseBody;
+          try {
+            responseBody = JSON.parse(Buffer.concat(bodyChunks).toString());
+          } catch (e) {
+            reject(e);
+          }
+
+          if (res.statusCode && res.statusCode > 299) {
+            reject('Error calling App Search API: ' + JSON.stringify(responseBody));
+          }
+
+          resolve(responseBody);
+        });
+      }
+    );
+
+    req.on('error', (e) => {
+      reject(e);
+    });
+
+    if (postData) {
+      req.write(postData);
+    }
+    req.end();
+  });
+};
+
+export interface IEngine {
+  name: string;
+}
+
+export const createEngine = async (engineName: string): Promise<IEngine> => {
+  return await makeRequest('POST', '/api/as/v1/engines', { name: engineName });
+};
+
+export const destroyEngine = async (engineName: string): Promise<object> => {
+  return await makeRequest('DELETE', `/api/as/v1/engines/${engineName}`);
+};
+
+export const createMetaEngine = async (
+  engineName: string,
+  sourceEngines: string[]
+): Promise<IEngine> => {
+  return await makeRequest('POST', '/api/as/v1/engines', {
+    name: engineName,
+    type: 'meta',
+    source_engines: sourceEngines,
+  });
+};
+
+export interface ISearchResponse {
+  results: object[];
+}
+
+const search = async (engineName: string): Promise<ISearchResponse> => {
+  return await makeRequest('POST', `/api/as/v1/engines/${engineName}/search`, { query: '' });
+};
+
+// Since the App Search API does not issue document receipts, the only way to tell whether or not documents
+// are fully indexed is to poll the search endpoint.
+export const waitForIndexedDocs = (engineName: string) => {
+  return new Promise(async function (resolve) {
+    let isReady = false;
+    while (!isReady) {
+      const response = await search(engineName);
+      if (response.results && response.results.length > 0) {
+        isReady = true;
+        resolve();
+      }
+    }
+  });
+};
+
+export const indexData = async (engineName: string, docs: object[]) => {
+  return await makeRequest('POST', `/api/as/v1/engines/${engineName}/documents`, docs);
+};
diff --git a/x-pack/test/functional_enterprise_search/services/app_search_service.ts b/x-pack/test/functional_enterprise_search/services/app_search_service.ts
new file mode 100644
index 0000000000000..9a43783402f4b
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/services/app_search_service.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { FtrProviderContext } from '../ftr_provider_context';
+
+const ENTERPRISE_SEARCH_USER = 'enterprise_search';
+const ENTERPRISE_SEARCH_PASSWORD = 'changeme';
+import {
+  createEngine,
+  createMetaEngine,
+  indexData,
+  waitForIndexedDocs,
+  destroyEngine,
+  IEngine,
+} from './app_search_client';
+
+export interface IUser {
+  user: string;
+  password: string;
+}
+export { IEngine };
+
+export class AppSearchService {
+  getEnterpriseSearchUser(): IUser {
+    return {
+      user: ENTERPRISE_SEARCH_USER,
+      password: ENTERPRISE_SEARCH_PASSWORD,
+    };
+  }
+
+  createEngine(): Promise<IEngine> {
+    const engineName = `test-engine-${new Date().getTime()}`;
+    return createEngine(engineName);
+  }
+
+  async createEngineWithDocs(): Promise<IEngine> {
+    const engine = await this.createEngine();
+    const docs = [
+      { id: 1, name: 'doc1' },
+      { id: 2, name: 'doc2' },
+      { id: 3, name: 'doc2' },
+    ];
+    await indexData(engine.name, docs);
+    await waitForIndexedDocs(engine.name);
+    return engine;
+  }
+
+  createMetaEngine(sourceEngines: string[]): Promise<IEngine> {
+    const engineName = `test-meta-engine-${new Date().getTime()}`;
+    return createMetaEngine(engineName, sourceEngines);
+  }
+
+  destroyEngine(engineName: string) {
+    return destroyEngine(engineName);
+  }
+}
+
+export async function AppSearchServiceProvider({ getService }: FtrProviderContext) {
+  const lifecycle = getService('lifecycle');
+  const security = getService('security');
+
+  lifecycle.beforeTests.add(async () => {
+    // The App Search plugin passes through the current user name and password
+    // through on the API call to App Search. Therefore, we need to be signed
+    // in as the enterprise_search user in order for this plugin to work.
+    await security.user.create(ENTERPRISE_SEARCH_USER, {
+      password: ENTERPRISE_SEARCH_PASSWORD,
+      roles: ['kibana_admin'],
+      full_name: ENTERPRISE_SEARCH_USER,
+    });
+  });
+
+  return new AppSearchService();
+}
diff --git a/x-pack/test/functional_enterprise_search/services/index.ts b/x-pack/test/functional_enterprise_search/services/index.ts
new file mode 100644
index 0000000000000..1715c98677ac6
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/services/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { services as functionalServices } from '../../functional/services';
+import { AppSearchServiceProvider } from './app_search_service';
+
+export const services = {
+  ...functionalServices,
+  appSearch: AppSearchServiceProvider,
+};
diff --git a/x-pack/test/functional_enterprise_search/with_host_configured.config.ts b/x-pack/test/functional_enterprise_search/with_host_configured.config.ts
new file mode 100644
index 0000000000000..f425f806f4bcd
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/with_host_configured.config.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { resolve } from 'path';
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+  const baseConfig = await readConfigFile(require.resolve('./base_config'));
+
+  return {
+    // default to the xpack functional config
+    ...baseConfig.getAll(),
+
+    testFiles: [resolve(__dirname, './apps/enterprise_search/with_host_configured')],
+
+    junit: {
+      reportName: 'X-Pack Enterprise Search Functional Tests with Host Configured',
+    },
+
+    kbnTestServer: {
+      ...baseConfig.get('kbnTestServer'),
+      serverArgs: [
+        ...baseConfig.get('kbnTestServer.serverArgs'),
+        '--enterpriseSearch.host=http://localhost:3002',
+      ],
+    },
+  };
+}
diff --git a/x-pack/test/functional_enterprise_search/without_host_configured.config.ts b/x-pack/test/functional_enterprise_search/without_host_configured.config.ts
new file mode 100644
index 0000000000000..0f2afd214abed
--- /dev/null
+++ b/x-pack/test/functional_enterprise_search/without_host_configured.config.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { resolve } from 'path';
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+  const baseConfig = await readConfigFile(require.resolve('./base_config'));
+
+  return {
+    // default to the xpack functional config
+    ...baseConfig.getAll(),
+
+    testFiles: [resolve(__dirname, './apps/enterprise_search/without_host_configured')],
+
+    junit: {
+      reportName: 'X-Pack Enterprise Search Functional Tests without Host Configured',
+    },
+  };
+}
diff --git a/x-pack/test/ui_capabilities/common/nav_links_builder.ts b/x-pack/test/ui_capabilities/common/nav_links_builder.ts
index 405ef4dbdc5b1..b20a499ba7e20 100644
--- a/x-pack/test/ui_capabilities/common/nav_links_builder.ts
+++ b/x-pack/test/ui_capabilities/common/nav_links_builder.ts
@@ -15,6 +15,10 @@ export class NavLinksBuilder {
       management: {
         navLinkId: 'kibana:stack_management',
       },
+      // TODO: Temp until navLinkIds fix is merged in
+      appSearch: {
+        navLinkId: 'appSearch',
+      },
     };
   }
 
diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts
index f8f3f2be2b2ec..0e0d46c6ce2cd 100644
--- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts
+++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts
@@ -32,17 +32,27 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
             break;
           }
           case 'global_all at everything_space':
-          case 'dual_privileges_all at everything_space':
+          case 'dual_privileges_all at everything_space': {
+            expect(uiCapabilities.success).to.be(true);
+            expect(uiCapabilities.value).to.have.property('catalogue');
+            // everything except ml and monitoring is enabled
+            const expected = mapValues(
+              uiCapabilities.value!.catalogue,
+              (enabled, catalogueId) => catalogueId !== 'ml' && catalogueId !== 'monitoring'
+            );
+            expect(uiCapabilities.value!.catalogue).to.eql(expected);
+            break;
+          }
           case 'everything_space_all at everything_space':
           case 'global_read at everything_space':
           case 'dual_privileges_read at everything_space':
           case 'everything_space_read at everything_space': {
             expect(uiCapabilities.success).to.be(true);
             expect(uiCapabilities.value).to.have.property('catalogue');
-            // everything except ml and monitoring is enabled
+            // everything except ml and monitoring and enterprise search is enabled
             const expected = mapValues(
               uiCapabilities.value!.catalogue,
-              (enabled, catalogueId) => catalogueId !== 'ml' && catalogueId !== 'monitoring'
+              (enabled, catalogueId) => !['ml', 'monitoring', 'appSearch'].includes(catalogueId)
             );
             expect(uiCapabilities.value!.catalogue).to.eql(expected);
             break;
diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts
index 10ecf5d25d346..08a7d789153e7 100644
--- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts
+++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts
@@ -38,14 +38,20 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
             break;
           case 'global_all at everything_space':
           case 'dual_privileges_all at everything_space':
-          case 'dual_privileges_read at everything_space':
-          case 'global_read at everything_space':
+            expect(uiCapabilities.success).to.be(true);
+            expect(uiCapabilities.value).to.have.property('navLinks');
+            expect(uiCapabilities.value!.navLinks).to.eql(
+              navLinksBuilder.except('ml', 'monitoring')
+            );
+            break;
           case 'everything_space_all at everything_space':
+          case 'global_read at everything_space':
+          case 'dual_privileges_read at everything_space':
           case 'everything_space_read at everything_space':
             expect(uiCapabilities.success).to.be(true);
             expect(uiCapabilities.value).to.have.property('navLinks');
             expect(uiCapabilities.value!.navLinks).to.eql(
-              navLinksBuilder.except('ml', 'monitoring')
+              navLinksBuilder.except('ml', 'monitoring', 'enterpriseSearch', 'appSearch')
             );
             break;
           case 'superuser at nothing_space':
diff --git a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts
index 52a1f30147b4f..99f91407dc1d2 100644
--- a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts
+++ b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts
@@ -32,9 +32,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
             break;
           }
           case 'all':
-          case 'read':
-          case 'dual_privileges_all':
-          case 'dual_privileges_read': {
+          case 'dual_privileges_all': {
             expect(uiCapabilities.success).to.be(true);
             expect(uiCapabilities.value).to.have.property('catalogue');
             // everything except ml and monitoring is enabled
@@ -45,6 +43,18 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
             expect(uiCapabilities.value!.catalogue).to.eql(expected);
             break;
           }
+          case 'read':
+          case 'dual_privileges_read': {
+            expect(uiCapabilities.success).to.be(true);
+            expect(uiCapabilities.value).to.have.property('catalogue');
+            // everything except ml and monitoring and enterprise search is enabled
+            const expected = mapValues(
+              uiCapabilities.value!.catalogue,
+              (enabled, catalogueId) => !['ml', 'monitoring', 'appSearch'].includes(catalogueId)
+            );
+            expect(uiCapabilities.value!.catalogue).to.eql(expected);
+            break;
+          }
           case 'foo_all':
           case 'foo_read': {
             expect(uiCapabilities.success).to.be(true);
diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts
index fe9ffa9286de8..d3bd2e1afd357 100644
--- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts
+++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts
@@ -37,15 +37,21 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
             expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all());
             break;
           case 'all':
-          case 'read':
           case 'dual_privileges_all':
-          case 'dual_privileges_read':
             expect(uiCapabilities.success).to.be(true);
             expect(uiCapabilities.value).to.have.property('navLinks');
             expect(uiCapabilities.value!.navLinks).to.eql(
               navLinksBuilder.except('ml', 'monitoring')
             );
             break;
+          case 'read':
+          case 'dual_privileges_read':
+            expect(uiCapabilities.success).to.be(true);
+            expect(uiCapabilities.value).to.have.property('navLinks');
+            expect(uiCapabilities.value!.navLinks).to.eql(
+              navLinksBuilder.except('ml', 'monitoring', 'appSearch')
+            );
+            break;
           case 'foo_all':
           case 'foo_read':
             expect(uiCapabilities.success).to.be(true);