diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9bba980268a..2f5d5ee061f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,8 +32,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 - [Multiple Datasource] Test connection schema validation for registered auth types ([#6109](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6109))
 - [Workspace] Consume workspace id in saved object client ([#6014](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6014))
 - [Multiple Datasource] Export DataSourcePluginRequestContext at top level for plugins to use ([#6108](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6108))
-
 - [Workspace] Add delete saved objects by workspace functionality([#6013](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6013))
+- [Workspace] Add a workspace client in workspace plugin ([#6094](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6094))
 
 ### 🐛 Bug Fixes
 
diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts
index e1a45ee115ab..8c869415aede 100644
--- a/src/plugins/workspace/public/plugin.test.ts
+++ b/src/plugins/workspace/public/plugin.test.ts
@@ -3,10 +3,26 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import { coreMock } from '../../../core/public/mocks';
+import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.mock';
+import { chromeServiceMock, coreMock } from '../../../core/public/mocks';
 import { WorkspacePlugin } from './plugin';
 
 describe('Workspace plugin', () => {
+  const getSetupMock = () => ({
+    ...coreMock.createSetup(),
+    chrome: chromeServiceMock.createSetupContract(),
+  });
+  beforeEach(() => {
+    WorkspaceClientMock.mockClear();
+    Object.values(workspaceClientMock).forEach((item) => item.mockClear());
+  });
+  it('#setup', async () => {
+    const setupMock = getSetupMock();
+    const workspacePlugin = new WorkspacePlugin();
+    await workspacePlugin.setup(setupMock);
+    expect(WorkspaceClientMock).toBeCalledTimes(1);
+  });
+
   it('#call savedObjectsClient.setCurrentWorkspace when current workspace id changed', () => {
     const workspacePlugin = new WorkspacePlugin();
     const coreStart = coreMock.createStart();
diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts
index 3840066fcee3..6f604bcf5678 100644
--- a/src/plugins/workspace/public/plugin.ts
+++ b/src/plugins/workspace/public/plugin.ts
@@ -4,7 +4,8 @@
  */
 
 import type { Subscription } from 'rxjs';
-import { Plugin, CoreStart } from '../../../core/public';
+import { Plugin, CoreStart, CoreSetup } from '../../../core/public';
+import { WorkspaceClient } from './workspace_client';
 
 export class WorkspacePlugin implements Plugin<{}, {}, {}> {
   private coreStart?: CoreStart;
@@ -18,7 +19,9 @@ export class WorkspacePlugin implements Plugin<{}, {}, {}> {
       });
     }
   }
-  public async setup() {
+  public async setup(core: CoreSetup) {
+    const workspaceClient = new WorkspaceClient(core.http, core.workspaces);
+    await workspaceClient.init();
     return {};
   }
 
diff --git a/src/plugins/workspace/public/workspace_client.mock.ts b/src/plugins/workspace/public/workspace_client.mock.ts
new file mode 100644
index 000000000000..2ceeae5627d1
--- /dev/null
+++ b/src/plugins/workspace/public/workspace_client.mock.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export const workspaceClientMock = {
+  init: jest.fn(),
+  enterWorkspace: jest.fn(),
+  getCurrentWorkspaceId: jest.fn(),
+  getCurrentWorkspace: jest.fn(),
+  create: jest.fn(),
+  delete: jest.fn(),
+  list: jest.fn(),
+  get: jest.fn(),
+  update: jest.fn(),
+  stop: jest.fn(),
+};
+
+export const WorkspaceClientMock = jest.fn(function () {
+  return workspaceClientMock;
+});
+
+jest.doMock('./workspace_client', () => ({
+  WorkspaceClient: WorkspaceClientMock,
+}));
diff --git a/src/plugins/workspace/public/workspace_client.test.ts b/src/plugins/workspace/public/workspace_client.test.ts
new file mode 100644
index 000000000000..c18ed3db64e7
--- /dev/null
+++ b/src/plugins/workspace/public/workspace_client.test.ts
@@ -0,0 +1,212 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { httpServiceMock, workspacesServiceMock } from '../../../core/public/mocks';
+import { WorkspaceClient } from './workspace_client';
+
+const getWorkspaceClient = () => {
+  const httpSetupMock = httpServiceMock.createSetupContract();
+  const workspaceMock = workspacesServiceMock.createSetupContract();
+  return {
+    httpSetupMock,
+    workspaceMock,
+    workspaceClient: new WorkspaceClient(httpSetupMock, workspaceMock),
+  };
+};
+
+describe('#WorkspaceClient', () => {
+  it('#init', async () => {
+    const { workspaceClient, httpSetupMock, workspaceMock } = getWorkspaceClient();
+    await workspaceClient.init();
+    expect(workspaceMock.initialized$.getValue()).toEqual(true);
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
+      method: 'POST',
+      body: JSON.stringify({
+        perPage: 999,
+      }),
+    });
+  });
+
+  it('#enterWorkspace', async () => {
+    const { workspaceClient, httpSetupMock, workspaceMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: false,
+    });
+    const result = await workspaceClient.enterWorkspace('foo');
+    expect(result.success).toEqual(false);
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+    });
+    const successResult = await workspaceClient.enterWorkspace('foo');
+    expect(workspaceMock.currentWorkspaceId$.getValue()).toEqual('foo');
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/foo', {
+      method: 'GET',
+    });
+    expect(successResult.success).toEqual(true);
+  });
+
+  it('#getCurrentWorkspaceId', async () => {
+    const { workspaceClient, httpSetupMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+    });
+    await workspaceClient.enterWorkspace('foo');
+    expect(workspaceClient.getCurrentWorkspaceId()).toEqual({
+      success: true,
+      result: 'foo',
+    });
+  });
+
+  it('#getCurrentWorkspace', async () => {
+    const { workspaceClient, httpSetupMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+      result: {
+        name: 'foo',
+      },
+    });
+    await workspaceClient.enterWorkspace('foo');
+    expect(await workspaceClient.getCurrentWorkspace()).toEqual({
+      success: true,
+      result: {
+        name: 'foo',
+      },
+    });
+  });
+
+  it('#create', async () => {
+    const { workspaceClient, httpSetupMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+      result: {
+        name: 'foo',
+        workspaces: [],
+      },
+    });
+    await workspaceClient.create({
+      name: 'foo',
+    });
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces', {
+      method: 'POST',
+      body: JSON.stringify({
+        attributes: {
+          name: 'foo',
+        },
+      }),
+    });
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
+      method: 'POST',
+      body: JSON.stringify({
+        perPage: 999,
+      }),
+    });
+  });
+
+  it('#delete', async () => {
+    const { workspaceClient, httpSetupMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+      result: {
+        name: 'foo',
+        workspaces: [],
+      },
+    });
+    await workspaceClient.delete('foo');
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/foo', {
+      method: 'DELETE',
+    });
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
+      method: 'POST',
+      body: JSON.stringify({
+        perPage: 999,
+      }),
+    });
+  });
+
+  it('#list', async () => {
+    const { workspaceClient, httpSetupMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+      result: {
+        workspaces: [],
+      },
+    });
+    await workspaceClient.list({
+      perPage: 999,
+    });
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
+      method: 'POST',
+      body: JSON.stringify({
+        perPage: 999,
+      }),
+    });
+  });
+
+  it('#get', async () => {
+    const { workspaceClient, httpSetupMock } = getWorkspaceClient();
+    await workspaceClient.get('foo');
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/foo', {
+      method: 'GET',
+    });
+  });
+
+  it('#update', async () => {
+    const { workspaceClient, httpSetupMock, workspaceMock } = getWorkspaceClient();
+    httpSetupMock.fetch.mockResolvedValue({
+      success: true,
+      result: {
+        workspaces: [
+          {
+            id: 'foo',
+          },
+        ],
+      },
+    });
+    await workspaceClient.update('foo', {
+      name: 'foo',
+    });
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/foo', {
+      method: 'PUT',
+      body: JSON.stringify({
+        attributes: {
+          name: 'foo',
+        },
+      }),
+    });
+    expect(workspaceMock.workspaceList$.getValue()).toEqual([
+      {
+        id: 'foo',
+      },
+    ]);
+    expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
+      method: 'POST',
+      body: JSON.stringify({
+        perPage: 999,
+      }),
+    });
+  });
+
+  it('#update with list gives error', async () => {
+    const { workspaceClient, httpSetupMock, workspaceMock } = getWorkspaceClient();
+    let callTimes = 0;
+    httpSetupMock.fetch.mockImplementation(async () => {
+      callTimes++;
+      if (callTimes > 1) {
+        return {
+          success: false,
+          error: 'Something went wrong',
+        };
+      }
+
+      return {
+        success: true,
+      };
+    });
+    await workspaceClient.update('foo', {
+      name: 'foo',
+    });
+    expect(workspaceMock.workspaceList$.getValue()).toEqual([]);
+  });
+});
diff --git a/src/plugins/workspace/public/workspace_client.ts b/src/plugins/workspace/public/workspace_client.ts
new file mode 100644
index 000000000000..3e988f38b265
--- /dev/null
+++ b/src/plugins/workspace/public/workspace_client.ts
@@ -0,0 +1,294 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { i18n } from '@osd/i18n';
+import {
+  HttpFetchError,
+  HttpFetchOptions,
+  HttpSetup,
+  WorkspaceAttribute,
+  WorkspacesSetup,
+} from '../../../core/public';
+
+const WORKSPACES_API_BASE_URL = '/api/workspaces';
+
+const join = (...uriComponents: Array<string | undefined>) =>
+  uriComponents
+    .filter((comp): comp is string => Boolean(comp))
+    .map(encodeURIComponent)
+    .join('/');
+
+type IResponse<T> =
+  | {
+      result: T;
+      success: true;
+    }
+  | {
+      success: false;
+      error?: string;
+    };
+
+interface WorkspaceFindOptions {
+  page?: number;
+  perPage?: number;
+  search?: string;
+  searchFields?: string[];
+  sortField?: string;
+  sortOrder?: string;
+}
+
+/**
+ * Workspaces is OpenSearchDashboards's visualize mechanism allowing admins to
+ * organize related features
+ *
+ * @public
+ */
+export class WorkspaceClient {
+  private http: HttpSetup;
+  private workspaces: WorkspacesSetup;
+
+  constructor(http: HttpSetup, workspaces: WorkspacesSetup) {
+    this.http = http;
+    this.workspaces = workspaces;
+  }
+
+  /**
+   * Initialize workspace list:
+   * 1. Retrieve the list of workspaces
+   * 2. Change the initialized flag to true
+   */
+  public async init() {
+    await this.updateWorkspaceList();
+    this.workspaces.initialized$.next(true);
+  }
+
+  /**
+   * Add a non-throw-error fetch method,
+   * so that consumers only need to care about
+   * if the success is false instead of wrapping the call with a try catch
+   * and judge the error both in catch clause and if(!success) cluase.
+   */
+  private safeFetch = async <T = any>(
+    path: string,
+    options: HttpFetchOptions
+  ): Promise<IResponse<T>> => {
+    try {
+      return await this.http.fetch<IResponse<T>>(path, options);
+    } catch (error: unknown) {
+      if (error instanceof HttpFetchError) {
+        return {
+          success: false,
+          error: error.body?.message || error.body?.error || error.message,
+        };
+      }
+
+      if (error instanceof Error) {
+        return {
+          success: false,
+          error: error.message,
+        };
+      }
+
+      return {
+        success: false,
+        error: 'Unknown error',
+      };
+    }
+  };
+
+  /**
+   * Filter empty sub path and join all of the sub paths into a standard http path
+   *
+   * @param path
+   * @returns path
+   */
+  private getPath(...path: Array<string | undefined>): string {
+    return [WORKSPACES_API_BASE_URL, join(...path)].filter((item) => item).join('/');
+  }
+
+  /**
+   * Fetch latest list of workspaces and update workspaceList$ to notify subscriptions
+   */
+  private async updateWorkspaceList(): Promise<void> {
+    const result = await this.list({
+      perPage: 999,
+    });
+
+    if (result?.success) {
+      this.workspaces.workspaceList$.next(result.result.workspaces);
+    } else {
+      this.workspaces.workspaceList$.next([]);
+    }
+  }
+
+  /**
+   * This method will check if a valid workspace can be found by the given workspace id,
+   * If so, perform a side effect of updating the core.workspace.currentWorkspaceId$.
+   *
+   * @param id workspace id
+   * @returns {Promise<IResponse<null>>} result for this operation
+   */
+  public async enterWorkspace(id: string): Promise<IResponse<null>> {
+    const workspaceResp = await this.get(id);
+    if (workspaceResp.success) {
+      this.workspaces.currentWorkspaceId$.next(id);
+      return {
+        success: true,
+        result: null,
+      };
+    } else {
+      return workspaceResp;
+    }
+  }
+
+  /**
+   * A bypass layer to get current workspace id
+   */
+  public getCurrentWorkspaceId(): IResponse<WorkspaceAttribute['id']> {
+    const currentWorkspaceId = this.workspaces.currentWorkspaceId$.getValue();
+    if (!currentWorkspaceId) {
+      return {
+        success: false,
+        error: i18n.translate('workspace.error.notInWorkspace', {
+          defaultMessage: 'You are not in any workspace yet.',
+        }),
+      };
+    }
+
+    return {
+      success: true,
+      result: currentWorkspaceId,
+    };
+  }
+
+  /**
+   * Do a find in the latest workspace list with current workspace id
+   */
+  public async getCurrentWorkspace(): Promise<IResponse<WorkspaceAttribute>> {
+    const currentWorkspaceIdResp = this.getCurrentWorkspaceId();
+    if (currentWorkspaceIdResp.success) {
+      const currentWorkspaceResp = await this.get(currentWorkspaceIdResp.result);
+      return currentWorkspaceResp;
+    } else {
+      return currentWorkspaceIdResp;
+    }
+  }
+
+  /**
+   * Create a workspace
+   *
+   * @param attributes
+   * @returns {Promise<IResponse<Pick<WorkspaceAttribute, 'id'>>>} id of the new created workspace
+   */
+  public async create(
+    attributes: Omit<WorkspaceAttribute, 'id'>
+  ): Promise<IResponse<Pick<WorkspaceAttribute, 'id'>>> {
+    const path = this.getPath();
+
+    const result = await this.safeFetch<WorkspaceAttribute>(path, {
+      method: 'POST',
+      body: JSON.stringify({
+        attributes,
+      }),
+    });
+
+    if (result.success) {
+      await this.updateWorkspaceList();
+    }
+
+    return result;
+  }
+
+  /**
+   * Deletes a workspace by workspace id
+   *
+   * @param id
+   * @returns {Promise<IResponse<null>>} result for this operation
+   */
+  public async delete(id: string): Promise<IResponse<null>> {
+    const result = await this.safeFetch<null>(this.getPath(id), { method: 'DELETE' });
+
+    if (result.success) {
+      await this.updateWorkspaceList();
+    }
+
+    return result;
+  }
+
+  /**
+   * Search for workspaces
+   *
+   * @param {object} [options={}]
+   * @property {string} options.search
+   * @property {string} options.searchFields - see OpenSearch Simple Query String
+   *                                        Query field argument for more information
+   * @property {integer} [options.page=1]
+   * @property {integer} [options.perPage=20]
+   * @property {array} options.fields
+   * @returns A find result with workspaces matching the specified search.
+   */
+  public list(
+    options?: WorkspaceFindOptions
+  ): Promise<
+    IResponse<{
+      workspaces: WorkspaceAttribute[];
+      total: number;
+      per_page: number;
+      page: number;
+    }>
+  > {
+    const path = this.getPath('_list');
+    return this.safeFetch(path, {
+      method: 'POST',
+      body: JSON.stringify(options || {}),
+    });
+  }
+
+  /**
+   * Fetches a single workspace by a workspace id
+   *
+   * @param {string} id
+   * @returns {Promise<IResponse<WorkspaceAttribute>>} The metadata of the workspace for the given id.
+   */
+  public get(id: string): Promise<IResponse<WorkspaceAttribute>> {
+    const path = this.getPath(id);
+    return this.safeFetch(path, {
+      method: 'GET',
+    });
+  }
+
+  /**
+   * Updates a workspace
+   *
+   * @param {string} id
+   * @param {object} attributes
+   * @returns {Promise<IResponse<boolean>>} result for this operation
+   */
+  public async update(
+    id: string,
+    attributes: Partial<WorkspaceAttribute>
+  ): Promise<IResponse<boolean>> {
+    const path = this.getPath(id);
+    const body = {
+      attributes,
+    };
+
+    const result = await this.safeFetch(path, {
+      method: 'PUT',
+      body: JSON.stringify(body),
+    });
+
+    if (result.success) {
+      await this.updateWorkspaceList();
+    }
+
+    return result;
+  }
+
+  public stop() {
+    this.workspaces.workspaceList$.unsubscribe();
+    this.workspaces.currentWorkspaceId$.unsubscribe();
+  }
+}