diff --git a/docs/extensions/README.md b/docs/extensions/README.md
new file mode 100644
index 0000000000..f8906d75ee
--- /dev/null
+++ b/docs/extensions/README.md
@@ -0,0 +1,46 @@
+# Lens Extension API
+
+Customize and enhance the Lens experience with the Lens Extension API.
+Use the extension API to create menus or page content.
+The same extension API was used to create many of Lens's core features.
+To install your first extension you should goto the [extension page](lens://app/extensions) in lens.
+
+This documentation describes:
+
+* How to build, run, test, and publish an extension.
+* How to take full advantage of the Lens Extension API.
+* Where to find [guides](guides/README.md) and [code samples](https://github.com/lensapp/lens-extension-samples) to help get you started.
+
+## What Extensions Can Do
+
+Here are some examples of what you can achieve with the Extension API:
+
+* Add custom components & views in the UI - Extending the Lens Workbench
+
+For an overview of the Lens Extension API, refer to the [Common Capabilities](capabilities/common-capabilities.md) page. [Extension Guides Overview](guides/README.md) also includes a list of code samples and guides that illustrate various ways of using the Lens Extension API.
+
+## How to Build Extensions
+
+Here is what each section of the Lens Extension API docs can help you with:
+
+* **Getting Started** teaches fundamental concepts for building extensions with the Hello World sample.
+* **Extension Capabilities** dissects Lens's Extension API into smaller categories and points you to more detailed topics.
+* **Extension Guides** includes guides and code samples that explain specific usages of Lens Extension API.
+* **Testing and Publishing** includes in-depth guides on various extension development topics, such as testing and publishing extensions.
+* **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
+
+## What's New
+
+Just like Lens itself, the extension API updates on a monthly cadence, rolling out new features with every release.
+
+Keep up with Lens and the Lens Extension API by reviewing the [release notes](https://github.com/lensapp/lens/releases).
+
+## Looking for Help
+
+If you have questions for extension development, try asking on the [Lens Dev Slack](http://k8slens.slack.com/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time.
+
+To provide feedback on the documentation or issues with the Lens Extension API, create new issues at [lensapp/lens](https://github.com/lensapp/lens/issues). Please use the labels `area/documentation` and/or `area/extension`.
+
+## Downloading Lens
+
+[Download Lens](https://github.com/lensapp/lens/releases) for macOS, Windows, or Linux.
diff --git a/docs/extensions/capabilities/README.md b/docs/extensions/capabilities/README.md
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/extensions/capabilities/common-capabilities.md b/docs/extensions/capabilities/common-capabilities.md
new file mode 100644
index 0000000000..676603ce21
--- /dev/null
+++ b/docs/extensions/capabilities/common-capabilities.md
@@ -0,0 +1,283 @@
+# Common Capabilities
+
+Here we will discuss common and important building blocks for your extensions, and explain how you can use them.
+Almost all extensions use some of these functionalities.
+
+## Main Extension
+
+The main extension runs in the background.
+It adds app menu items to the Lens UI.
+In order to see logs from this extension, you need to start Lens from the command line.
+
+### Activate
+
+This extension can register a custom callback that is executed when an extension is activated (started).
+
+``` javascript
+import { LensMainExtension } from "@k8slens/extensions"
+
+export default class ExampleMainExtension extends LensMainExtension {
+ async onActivate() {
+ console.log("hello world")
+ }
+}
+```
+
+### Deactivate
+
+This extension can register a custom callback that is executed when an extension is deactivated (stopped).
+
+``` javascript
+import { LensMainExtension } from "@k8slens/extensions"
+
+export default class ExampleMainExtension extends LensMainExtension {
+ async onDeactivate() {
+ console.log("bye bye")
+ }
+}
+```
+
+### App Menus
+
+This extension can register custom app menus that will be displayed on OS native menus.
+
+Example:
+
+```typescript
+import { LensMainExtension, windowManager } from "@k8slens/extensions"
+
+export default class ExampleMainExtension extends LensMainExtension {
+ appMenus = [
+ {
+ parentId: "help",
+ label: "Example item",
+ click() {
+ windowManager.navigate("https://k8slens.dev");
+ }
+ }
+ ]
+}
+```
+
+## Renderer Extension
+
+The renderer extension runs in a browser context, and is visible in Lens's main window.
+In order to see logs from this extension you need to check them via **View** > **Toggle Developer Tools** > **Console**.
+
+### Activate
+
+This extension can register a custom callback that is executed when an extension is activated (started).
+
+``` javascript
+import { LensRendererExtension } from "@k8slens/extensions"
+
+export default class ExampleExtension extends LensRendererExtension {
+ async onActivate() {
+ console.log("hello world")
+ }
+}
+```
+
+### Deactivate
+
+This extension can register a custom callback that is executed when an extension is deactivated (stopped).
+
+``` javascript
+import { LensRendererExtension } from "@k8slens/extensions"
+
+export default class ExampleMainExtension extends LensRendererExtension {
+ async onDeactivate() {
+ console.log("bye bye")
+ }
+}
+```
+
+### Global Pages
+
+This extension can register custom global pages (views) to Lens's main window.
+The global page is a full-screen page that hides all other content from a window.
+
+```typescript
+import React from "react"
+import { Component, LensRendererExtension } from "@k8slens/extensions"
+import { ExamplePage } from "./src/example-page"
+
+export default class ExampleRendererExtension extends LensRendererExtension {
+ globalPages = [
+ {
+ id: "example",
+ components: {
+ Page: ExamplePage,
+ }
+ }
+ ]
+
+ globalPageMenus = [
+ {
+ title: "Example page", // used in icon's tooltip
+ target: { pageId: "example" }
+ components: {
+ Icon: () => ,
+ }
+ }
+ ]
+}
+```
+
+### App Preferences
+
+This extension can register custom app preferences.
+It is responsible for storing a state for custom preferences.
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions"
+import { myCustomPreferencesStore } from "./src/my-custom-preferences-store"
+import { MyCustomPreferenceHint, MyCustomPreferenceInput } from "./src/my-custom-preference"
+
+
+export default class ExampleRendererExtension extends LensRendererExtension {
+ appPreferences = [
+ {
+ title: "My Custom Preference",
+ components: {
+ Hint: () => ,
+ Input: () =>
+ }
+ }
+ ]
+}
+```
+
+### Cluster Pages
+
+This extension can register custom cluster pages.
+These pages are visible in a cluster menu when a cluster is opened.
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExampleIcon, ExamplePage } from "./src/page"
+
+export default class ExampleExtension extends LensRendererExtension {
+ clusterPages = [
+ {
+ id: "extension-example", // optional
+ exact: true, // optional
+ components: {
+ Page: () => ,
+ }
+ }
+ ]
+
+ clusterPageMenus = [
+ {
+ url: "/extension-example", // optional
+ title: "Example Extension",
+ components: {
+ Icon: ExampleIcon,
+ }
+ }
+ ]
+}
+
+```
+
+### Cluster Features
+
+This extension can register installable features for a cluster.
+These features are visible in the "Cluster Settings" page.
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions"
+import { MyCustomFeature } from "./src/my-custom-feature"
+
+export default class ExampleExtension extends LensRendererExtension {
+ clusterFeatures = [
+ {
+ title: "My Custom Feature",
+ components: {
+ Description: () => {
+ return (
+
+ Just an example.
+
+ )
+ }
+ },
+ feature: new MyCustomFeature()
+ }
+ ]
+}
+
+```
+
+### Status Bar Items
+
+This extension can register custom icons and text to a status bar area.
+
+```typescript
+import React from "react";
+import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions";
+
+export default class ExampleExtension extends LensRendererExtension {
+ statusBarItems = [
+ {
+ components: {
+ Item: (
+
this.navigate("/example-page")} >
+
+
+ )
+ }
+ }
+ ]
+}
+
+```
+
+### Kubernetes Object Menu Items
+
+This extension can register custom menu items (actions) for specified Kubernetes kinds/apiVersions.
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions";
+import { CustomMenuItem, CustomMenuItemProps } from "./src/custom-menu-item"
+
+export default class ExampleExtension extends LensRendererExtension {
+ kubeObjectMenuItems = [
+ {
+ kind: "Node",
+ apiVersions: ["v1"],
+ components: {
+ MenuItem: (props: CustomMenuItemProps) =>
+ }
+ }
+ ]
+}
+
+```
+
+### Kubernetes Object Details
+
+This extension can register custom details (content) for specified Kubernetes kinds/apiVersions.
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions";
+import { CustomKindDetails, CustomKindDetailsProps } from "./src/custom-kind-details"
+
+export default class ExampleExtension extends LensRendererExtension {
+ kubeObjectDetailItems = [
+ {
+ kind: "CustomKind",
+ apiVersions: ["custom.acme.org/v1"],
+ components: {
+ Details: (props: CustomKindDetailsProps) =>
+ }
+ }
+ ]
+}
+```
diff --git a/docs/extensions/capabilities/images/css-vars-in-devtools.png b/docs/extensions/capabilities/images/css-vars-in-devtools.png
new file mode 100644
index 0000000000..a9df97e6bb
Binary files /dev/null and b/docs/extensions/capabilities/images/css-vars-in-devtools.png differ
diff --git a/docs/extensions/capabilities/images/theme-selector.png b/docs/extensions/capabilities/images/theme-selector.png
new file mode 100644
index 0000000000..5c2eba3165
Binary files /dev/null and b/docs/extensions/capabilities/images/theme-selector.png differ
diff --git a/docs/extensions/capabilities/styling.md b/docs/extensions/capabilities/styling.md
new file mode 100644
index 0000000000..62dbddde1a
--- /dev/null
+++ b/docs/extensions/capabilities/styling.md
@@ -0,0 +1,163 @@
+# Styling an Extension
+
+Lens provides a set of global styles and UI components that can be used by any extension to preserve the look and feel of the application.
+
+## Layout
+
+For layout tasks, Lens uses the [flex.box](https://www.npmjs.com/package/flex.box) library which provides helpful class names to specify some of the [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) properties.
+For example, consider the following HTML and its associated CSS properties:
+
+```html
+
+```
+
+```css
+div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+```
+
+However, you are free to use any styling technique or framework you like, including [Emotion](https://github.com/emotion-js/emotion) or even plain CSS.
+
+### Layout Variables
+
+There is a set of CSS variables available for for basic layout needs.
+They are located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
+
+```css
+--unit: 8px;
+--padding: var(--unit);
+--margin: var(--unit);
+--border-radius: 3px;
+```
+
+These variables are intended to set consistent margins and paddings across components.
+For example:
+
+```css
+.status {
+ padding-left: calc(var(--padding) * 2);
+ border-radius: var(--border-radius);
+}
+```
+
+## Themes
+
+Lens uses two built-in themes defined in [the themes directory](https://github.com/lensapp/lens/tree/master/src/renderer/themes) – one light and one dark.
+
+### Theme Variables
+
+When Lens is loaded, it transforms the selected theme's `json` file into a list of [CSS Custom Properties (CSS Variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).
+This list then gets injected into the `:root` element so that any of the down-level components can use them.
+
+
+When the user changes the theme, the above process is repeated, and new CSS variables appear, replacing the previous ones.
+
+If you want to preserve Lens's native look and feel, with respect to the lightness or darkness of your extension, you can use the provided variables and built-in Lens components such as `Button`, `Select`, `Table`, and so on.
+
+There is a set of CSS variables available for extensions to use for theming.
+They are all located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
+
+```css
+--font-main: 'Roboto', 'Helvetica', 'Arial', sans-serif;
+--font-monospace: Lucida Console, Monaco, Consolas, monospace;
+--font-size-small: calc(1.5 * var(--unit));
+--font-size: calc(1.75 * var(--unit));
+--font-size-big: calc(2 * var(--unit));
+--font-weight-thin: 300;
+--font-weight-normal: 400;
+--font-weight-bold: 500;
+```
+
+as well as in [the theme modules](https://github.com/lensapp/lens/tree/master/src/renderer/themes):
+
+```
+--blue: #3d90ce;
+--magenta: #c93dce;
+--golden: #ffc63d;
+--halfGray: #87909c80;
+--primary: #3d90ce;
+--textColorPrimary: #555555;
+--textColorSecondary: #51575d;
+--textColorAccent: #333333;
+--borderColor: #c9cfd3;
+--borderFaintColor: #dfdfdf;
+--mainBackground: #f1f1f1;
+--contentColor: #ffffff;
+--layoutBackground: #e8e8e8;
+--layoutTabsBackground: #f8f8f8;
+--layoutTabsActiveColor: #333333;
+--layoutTabsLineColor: #87909c80;
+...
+```
+
+These variables can be used in the following form: `var(--magenta)`.
+For example:
+
+```css
+.status {
+ font-size: var(--font-size-small);
+ background-color: var(--colorSuccess);
+}
+```
+
+### Theme Switching
+
+When the light theme is active, the `` element gets a "theme-light" class, or: ``.
+If the class isn't there, the theme defaults to dark. The active theme can be changed in the **Preferences** page:
+
+
+There is a way of detect active theme and its changes in JS. [MobX observer function/decorator](https://github.com/mobxjs/mobx-react#observercomponent) can be used for this purpose.
+
+```js
+import React from "react"
+import { observer } from "mobx-react"
+import { App, Component, Theme } from "@k8slens/extensions";
+
+@observer
+export class SupportPage extends React.Component {
+ render() {
+ return (
+
+
Active theme is {Theme.getActiveTheme().name}
+
+ );
+ }
+}
+```
+
+`Theme` entity from `@k8slens/extensions` provides active theme object and `@observer` decorator makes component reactive - so it will rerender each time any of the observables (active theme in our case) will be changed.
+
+Working example provided in [Styling with Emotion](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) sample extension.
+
+## Injected Styles
+
+Every extension is affected by the list of default global styles defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss). These are basic browser resets and element styles, including:
+
+- setting the `box-sizing` property for every element
+- default text and background colors
+- default font sizes
+- basic heading (h1, h2, etc) formatting
+- custom scrollbar styling
+
+Extensions may overwrite these defaults if needed. They have low CSS specificity, so overriding them should be fairly easy.
+
+## CSS-in-JS
+
+If an extension uses a system like `Emotion` to work with styles, it can use CSS variables as follows:
+
+```javascript
+const Container = styled.div(() => ({
+ backgroundColor: 'var(--mainBackground)'
+}));
+```
+
+## Examples
+
+You can explore samples for each styling technique that you can use for extensions:
+
+- [Styling with Sass](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample)
+- [Styling with Emotion](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample)
+- [Styling with CSS Modules](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample)
diff --git a/docs/extensions/get-started/anatomy.md b/docs/extensions/get-started/anatomy.md
new file mode 100644
index 0000000000..f445e421bf
--- /dev/null
+++ b/docs/extensions/get-started/anatomy.md
@@ -0,0 +1,116 @@
+# Extension Anatomy
+
+In the [previous section](your-first-extension.md) you learned how to create your first extension.
+In this section you will learn how this extension works under the hood.
+
+The Hello World sample extension does three things:
+
+- Implements `onActivate()` and outputs a message to the console.
+- Implements `onDectivate()` and outputs a message to the console.
+- Registers `ClusterPage` so that the page is visible in the left-side menu of the cluster dashboard.
+
+Let's take a closer look at our Hello World sample's source code and see how these three things are achieved.
+
+## Extension File Structure
+
+```
+.
+├── .gitignore // Ignore build output and node_modules
+├── Makefile // Config for build tasks that compiles the extension
+├── README.md // Readable description of your extension's functionality
+├── src
+│ └── page.tsx // Extension's additional source code
+├── main.ts // Source code for extension's main entrypoint
+├── package.json // Extension manifest and dependencies
+├── renderer.tsx // Source code for extension's renderer entrypoint
+├── tsconfig.json // TypeScript configuration
+├── webpack.config.js // Webpack configuration
+```
+
+The extension directory contains the extension's entry files and a few configuration files.
+Three files: `package.json`, `main.ts` and `renderer.tsx` are essential to understanding the Hello World sample extension.
+We'll look at those first.
+
+### Extension Manifest
+
+Each Lens extension must have a `package.json` file.
+It contains a mix of Node.js fields, including scripts and dependencies, and Lens-specific fields such as `publisher` and `contributes`.
+Some of the most-important fields include:
+
+- `name` and `publisher`: Lens uses `@/` as a unique ID for the extension.
+For example, the Hello World sample has the ID `@lensapp-samples/helloworld-sample`.
+Lens uses this ID to uniquely identify your extension.
+- `main`: the extension's entry point run in `main` process.
+- `renderer`: the extension's entry point run in `renderer` process.
+- `engines.lens`: the minimum version of Lens API that the extension depends upon.
+
+``` javascript
+{
+ "name": "helloworld-sample",
+ "publisher": "lens-samples",
+ "version": "0.0.1",
+ "description": "Lens helloworld-sample",
+ "license": "MIT",
+ "homepage": "https://github.com/lensapp/lens-extension-samples",
+ "engines": {
+ "lens": "^4.0.0"
+ },
+ "main": "dist/main.js",
+ "renderer": "dist/renderer.js",
+ "scripts": {
+ "build": "webpack --config webpack.config.js",
+ "dev": "npm run build --watch"
+ },
+ "dependencies": {
+ "react-open-doodles": "^1.0.5"
+ },
+ "devDependencies": {
+ "@k8slens/extensions": "^4.0.0-alpha.2",
+ "ts-loader": "^8.0.4",
+ "typescript": "^4.0.3",
+ "@types/react": "^16.9.35",
+ "@types/node": "^12.0.0",
+ "webpack": "^4.44.2",
+ "webpack-cli": "^3.3.11"
+ }
+}
+```
+
+## Extension Entry Files
+
+Lens extensions can have two separate entry files.
+One file is used in the `main` process of the Lens application and the other is used in the `renderer` process.
+The `main` entry file exports the class that extends `LensMainExtension`, and the `renderer` entry file exports the class that extends `LensRendererExtension`.
+
+Both extension classes have `onActivate` and `onDeactivate` methods.
+The `onActivate` method is executed when your extension is activated.
+If you need to initialize something in your extension, this is where such an operation should occur.
+The `onDeactivate` method gives you a chance to clean up before your extension becomes deactivated.
+For extensions where explicit cleanup is not required, you don't need to override this method.
+However, if an extension needs to perform an operation when Lens is shutting down (or if the extension is disabled or uninstalled), this is the method where such an operation should occur.
+
+The Hello World sample extension does not do anything on the `main` process, so we'll focus on the `renderer` process, instead.
+On the `renderer` entry point, the Hello World sample extension defines the `Cluster Page` object.
+The `Cluster Page` object registers the `/extension-example` path, and this path renders the `ExamplePage` React component.
+It also registers the `MenuItem` component that displays the `ExampleIcon` React component and the "Hello World" text in the left-side menu of the cluster dashboard.
+These React components are defined in the additional `./src/page.tsx` file.
+
+``` typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExampleIcon, ExamplePage } from "./page"
+import React from "react"
+
+export default class ExampleExtension extends LensRendererExtension {
+ clusterPages = [
+ {
+ id: "extension-example",
+ components: {
+ Page: () => ,
+ }
+ }
+ ]
+}
+```
+
+The Hello World sample extension uses the `Cluster Page` capability, which is just one of the Lens extension API's capabilities.
+The [Common Capabilities](../capabilities/common-capabilities.md) page will help you home in on the right capabilities to use with your own extensions.
diff --git a/docs/extensions/get-started/overview.md b/docs/extensions/get-started/overview.md
new file mode 100644
index 0000000000..84777aafa0
--- /dev/null
+++ b/docs/extensions/get-started/overview.md
@@ -0,0 +1,27 @@
+# Extension Development Overview
+
+This is a general overview to how the development of an extension will proceed.
+For building extensions there will be a few things that you should have installed, and some other things that might be of help.
+
+### Required:
+- [Node.js](https://www.nodejs.org/en/)
+- [Git](https://www.git-scm.com/)
+- Some sort of text editor – we recommend [VSCode](https://code.visualstudio.com/)
+- We use [Webpack](https://www.webpack.js.org/) for compilation.
+All extension need to be at least compatible with a webpack system.
+
+### Recommended:
+
+All Lens extensions are javascript packages.
+We recommend that you program in [Typescript](https://www.typescriptlang.org/) because it catches many common errors.
+
+Lens is a standard [Electron](https://www.electronjs.org/) application with both main and renderer processes.
+An extension is made up of two parts, one for each of Lens's core processes.
+When an extension is loaded, each part is first loaded and issues a notification that it has been loaded.
+From there, the extension can start doing is work.
+
+Lens uses [React](https://www.reactjs.org/) as its UI framework and provides some of Lens's own components for reuse with extensions.
+An extension is responsible for the lifetime of any resources it spins up.
+If an extension's main part starts new processes they all must be stopped and cleaned up when the extension is deactivated or unloaded.
+
+See [Your First Extension](your-first-extension.md) to get started.
diff --git a/docs/extensions/get-started/wrapping-up.md b/docs/extensions/get-started/wrapping-up.md
new file mode 100644
index 0000000000..f4aa174476
--- /dev/null
+++ b/docs/extensions/get-started/wrapping-up.md
@@ -0,0 +1,24 @@
+# Wrapping Up
+
+In [Your First Extension](your-first-extension.md), you learned how to create and run an extension.
+In [Extension Anatomy](anatomy.md), you learned in detail how a basic extension works.
+This is just a glimpse into what can be created with Lens extensions.
+Below are some suggested routes for learning more.
+
+## Extension Capabilities
+
+In this section, you'll find information on common extension capabilities, styling information, and a color reference guide.
+Determine whether your idea for an extension is doable and get ideas for new extensions by reading through the [Common Capabilities](../capabilities/common-capabilities.md) page.
+
+## Guides and Samples
+
+Here you'll find a collection of sample extensions that you can use as a base to work from.
+Some of these samples include a detailed guide that explains the source code.
+You can find all samples and guides in the [lens-extension-samples](https://github.com/lensapp/lens-extension-samples) repository.
+
+## Testing and Publishing
+
+In this section, you can learn:
+
+* How to add [integration tests](../testing-and-publishing/testing.md) to your extension
+* How to [publish your extension](../testing-and-publishing/publishing.md)
diff --git a/docs/extensions/get-started/your-first-extension.md b/docs/extensions/get-started/your-first-extension.md
new file mode 100644
index 0000000000..45ed3f885f
--- /dev/null
+++ b/docs/extensions/get-started/your-first-extension.md
@@ -0,0 +1,100 @@
+# Your First Extension
+
+We recommend to always use [Yeoman generator for Lens Extension](https://github.com/lensapp/generator-lens-ext) to start new extension project.
+[Read the generator guide here](../guides/generator.md).
+
+If you want to setup the project manually, please continue reading.
+
+## First Extension
+
+In this topic, you'll learn the basics of building extensions by creating an extension that adds a "Hello World" page to a cluster menu.
+
+## Install the Extension
+
+To install the extension, clone the [Lens Extension samples](https://github.com/lensapp/lens-extension-samples) repository to your local machine:
+
+```sh
+git clone https://github.com/lensapp/lens-extension-samples.git
+```
+
+Next you need to create a symlink.
+A symlink connects the directory that Lens will monitor for user-installed extensions to the sample extension.
+In this case the sample extension is `helloworld-sample`.
+
+### Linux & macOS
+
+```sh
+mkdir -p ~/.k8slens/extensions
+cd ~/.k8slens/extensions
+ln -s lens-extension-samples/helloworld-sample helloworld-sample
+```
+
+### Windows
+
+Create the directory that Lens will monitor for user-installed extensions:
+
+```sh
+mkdir C:\Users\\.k8slens\extensions -force
+cd C:\Users\\.k8slens\extensions
+```
+
+If you have administrator rights, you can create symlink to the sample extension – in this case `helloworld-sample`:
+
+```sh
+cmd /c mklink /D helloworld-sample lens-extension-samples\helloworld-sample
+```
+
+Without administrator rights, you need to copy the extensions sample directory into `C:\Users\\.k8slens\extensions`:
+
+```
+Copy-Item 'lens-extension-samples\helloworld-sample' 'C:\Users\\.k8slens\extensions\helloworld-sample'
+```
+
+## Build the Extension
+
+To build the extension you can use `make` or run the `npm` commands manually:
+
+```sh
+cd /helloworld-sample
+make build
+```
+
+To run the `npm` commands, enter:
+
+```sh
+cd /helloworld-sample
+npm install
+npm run build
+```
+
+Optionally, automatically rebuild the extension by watching for changes to the source code.
+To do so, enter:
+
+```sh
+cd /helloworld-sample
+npm run dev
+```
+
+You must restart Lens for the extension to load.
+After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
+
+With Lens running, either connect to an existing cluster or [create a new one](../../clusters/adding-clusters.md).
+You will see the "Hello World" page in the left-side cluster menu.
+
+## Develop the Extension
+
+Finally, you'll make a change to the message that our Hello World sample extension displays:
+
+1. Navigate to `/helloworld-sample`.
+2. In `page.tsx`, change the message from `HelloWorld!` to `Hello Lens Extensions`.
+3. Rebuild the extension. If you used `npm run dev`, the extension will rebuild automatically.
+4. Reload the Lens window.
+5. Click on the Hello World page.
+6. The updated message will appear.
+
+## Next Steps
+
+In the [next topic](anatomy.md), we'll take a closer look at the source code of our Hello World sample.
+
+You can find the source code for this tutorial at: [lensapp/lens-extension-samples](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample).
+[Extension Guides](../guides/README.md) contains additional samples.
diff --git a/docs/extensions/guides/README.md b/docs/extensions/guides/README.md
new file mode 100644
index 0000000000..06bbbe9e3c
--- /dev/null
+++ b/docs/extensions/guides/README.md
@@ -0,0 +1,37 @@
+# Extension Guides
+
+This section explains how to use specific Lens Extension APIs.
+It includes detailed guides and code samples.
+For introductory information about the Lens Extension API, please see [Your First Extension](../get-started/your-first-extension.md).
+
+Each guide or code sample includes the following:
+
+- Clearly commented source code.
+- Instructions for running the sample extension.
+- An image showing the sample extension's appearance and usage.
+- A listing of the Extension API being used.
+- An explanation of the concepts relevant to the Extension.
+
+## Guides
+
+| Guide | APIs |
+| ----- | ----- |
+| [Generate new extension project](generator.md) ||
+| [Main process extension](main-extension.md) | LensMainExtension |
+| [Renderer process extension](renderer-extension.md) | LensRendererExtension |
+| [Stores](stores.md) | |
+| [Components](components.md) | |
+| [KubeObjectListLayout](kube-object-list-layout.md) | |
+| [Working with mobx](working-with-mobx.md) | |
+| [Protocol Handlers](protocol-handlers.md) | |
+
+## Samples
+
+| Sample | APIs |
+| ----- | ----- |
+[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension LensRendererExtension Component.Icon Component.IconProps |
+[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension Store.ClusterStore Store.workspaceStore |
+[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension LensRendererExtension Component.Icon Component.IconProps |
+[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension LensRendererExtension Component.Icon Component.IconProps |
+[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension LensRendererExtension Component.Icon Component.IconProps |
+[custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension K8sApi.KubeApi K8sApi.KubeObjectStore Component.KubeObjectListLayout Component.KubeObjectDetailsProps Component.IconProps |
diff --git a/docs/extensions/guides/anatomy.md b/docs/extensions/guides/anatomy.md
new file mode 100644
index 0000000000..cc7b84e256
--- /dev/null
+++ b/docs/extensions/guides/anatomy.md
@@ -0,0 +1,3 @@
+---
+WIP
+---
diff --git a/docs/extensions/guides/components.md b/docs/extensions/guides/components.md
new file mode 100644
index 0000000000..cc7b84e256
--- /dev/null
+++ b/docs/extensions/guides/components.md
@@ -0,0 +1,3 @@
+---
+WIP
+---
diff --git a/docs/extensions/guides/generator.md b/docs/extensions/guides/generator.md
new file mode 100644
index 0000000000..6e09117407
--- /dev/null
+++ b/docs/extensions/guides/generator.md
@@ -0,0 +1,75 @@
+# Lens Extension Generator
+
+The [Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) creates a directory with the necessary files for developing an extension.
+
+## Installing and Getting Started with the Generator
+
+To begin, install Yeoman and the Lens Extension Generator with the following command:
+
+```bash
+npm install -g yo generator-lens-ext
+```
+
+Run the generator by entering the following command: `yo lens-ext`.
+
+Answer the following questions:
+
+```bash
+# ? What type of extension do you want to create? New Extension (TypeScript)
+# ? What's the name of your extension? my-first-lens-ext
+# ? What's the description of your extension? My hello world extension
+# ? What's your extension's publisher name? @my-org/my-first-lens-ext
+# ? Initialize a git repository? Yes
+# ? Install dependencies after initialization? Yes
+# ? Which package manager to use? yarn
+# ? symlink created extension folder to ~/.k8slens/extensions (mac/linux) or :Users\\.k8slens\extensions (windows)? Yes
+```
+
+Next, you'll need to have webpack watch the `my-first-lens-ext` folder.
+Start webpack by entering:
+
+```bash
+cd my-first-lens-ext
+npm start # start the webpack server in watch mode
+```
+
+Open Lens and you will see a **Hello World** item in the left-side menu under **Custom Resources**:
+
+
+
+## Developing the Extension
+
+Next, you'll try changing the way the new menu item appears in the UI.
+You'll change it from "Hello World" to "Hello Lens".
+
+Open `my-first-lens-ext/renderer.tsx` and change the value of `title` from `"Hello World"` to `"Hello Lens"`:
+
+```typescript
+clusterPageMenus = [
+ {
+ target: { pageId: "hello" },
+ title: "Hello Lens",
+ components: {
+ Icon: ExampleIcon,
+ }
+ }
+]
+```
+
+Reload Lens and you will see that the menu item text has changed to "Hello Lens".
+To reload Lens, enter `CMD+R` on Mac and `Ctrl+R` on Windows/Linux.
+
+
+
+## Debugging the Extension
+
+To debug your extension, please see our instructions on [Testing Extensions](../testing-and-publishing/testing.md).
+
+## Next Steps
+
+To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
+
+If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
+You can find the Lens contribution guidelines [here](../../contributing/README.md).
+
+The Generator source code is hosted at [Github](https://github.com/lensapp/generator-lens-ext).
diff --git a/docs/extensions/guides/images/certificates-crd-list.png b/docs/extensions/guides/images/certificates-crd-list.png
new file mode 100644
index 0000000000..19c9558f71
Binary files /dev/null and b/docs/extensions/guides/images/certificates-crd-list.png differ
diff --git a/docs/extensions/guides/images/clusterpagemenus.png b/docs/extensions/guides/images/clusterpagemenus.png
new file mode 100644
index 0000000000..3ed1c79e5b
Binary files /dev/null and b/docs/extensions/guides/images/clusterpagemenus.png differ
diff --git a/docs/extensions/guides/images/globalpagemenus.png b/docs/extensions/guides/images/globalpagemenus.png
new file mode 100644
index 0000000000..e986cc32e9
Binary files /dev/null and b/docs/extensions/guides/images/globalpagemenus.png differ
diff --git a/docs/extensions/guides/images/hello-lens.png b/docs/extensions/guides/images/hello-lens.png
new file mode 100644
index 0000000000..5e2c0ac0a5
Binary files /dev/null and b/docs/extensions/guides/images/hello-lens.png differ
diff --git a/docs/extensions/guides/images/hello-world.png b/docs/extensions/guides/images/hello-world.png
new file mode 100644
index 0000000000..1a4a9c73a9
Binary files /dev/null and b/docs/extensions/guides/images/hello-world.png differ
diff --git a/docs/extensions/guides/images/kubeobjectdetailitem.png b/docs/extensions/guides/images/kubeobjectdetailitem.png
new file mode 100644
index 0000000000..e2d68f0c3b
Binary files /dev/null and b/docs/extensions/guides/images/kubeobjectdetailitem.png differ
diff --git a/docs/extensions/guides/images/kubeobjectdetailitemwithpods.png b/docs/extensions/guides/images/kubeobjectdetailitemwithpods.png
new file mode 100644
index 0000000000..9a91f230f3
Binary files /dev/null and b/docs/extensions/guides/images/kubeobjectdetailitemwithpods.png differ
diff --git a/docs/extensions/guides/images/kubeobjectmenuitem.png b/docs/extensions/guides/images/kubeobjectmenuitem.png
new file mode 100644
index 0000000000..f9f91675de
Binary files /dev/null and b/docs/extensions/guides/images/kubeobjectmenuitem.png differ
diff --git a/docs/extensions/guides/images/kubeobjectmenuitemdetail.png b/docs/extensions/guides/images/kubeobjectmenuitemdetail.png
new file mode 100644
index 0000000000..ab5f9ac0f0
Binary files /dev/null and b/docs/extensions/guides/images/kubeobjectmenuitemdetail.png differ
diff --git a/docs/extensions/guides/images/routing-diag.png b/docs/extensions/guides/images/routing-diag.png
new file mode 100644
index 0000000000..9185ce94d8
Binary files /dev/null and b/docs/extensions/guides/images/routing-diag.png differ
diff --git a/docs/extensions/guides/kube-object-list-layout.md b/docs/extensions/guides/kube-object-list-layout.md
new file mode 100644
index 0000000000..99f6796c91
--- /dev/null
+++ b/docs/extensions/guides/kube-object-list-layout.md
@@ -0,0 +1,287 @@
+# KubeObjectListLayout Sample
+
+In this guide we will learn how to list Kubernetes CRD objects on the cluster dashboard.
+You can see the complete source code for this guide [here](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page).
+
+
+
+Next, we will go the implementation through in steps.
+To achieve our goal, we need to:
+
+1. [Register ClusterPage and ClusterPageMenu objects](#register-objects-for-clustepages-and-clusterpagemenus)
+2. [List Certificate Objects on the Cluster Page](#list-certificate-objects-on-the-cluster-page)
+3. [Customize Details Panel](#customize-details-panel)
+
+## Register `clusterPage` and `clusterPageMenu` Objects
+
+First thing we need to do with our extension is to register new menu item in the cluster menu and create a cluster page that is opened when clicking the menu item.
+We will do this in our extension class `CrdSampleExtension` that is derived `LensRendererExtension` class:
+
+```typescript
+export default class CrdSampleExtension extends LensRendererExtension {
+}
+```
+
+To register menu item in the cluster menu we need to register `PageMenuRegistration` object.
+This object will register a menu item with "Certificates" text.
+It will also use `CertificateIcon` component to render an icon and navigate to cluster page that is having `certificates` page id.
+
+```typescript
+export function CertificateIcon(props: Component.IconProps) {
+ return
+}
+
+export default class CrdSampleExtension extends LensRendererExtension {
+
+ clusterPageMenus = [
+ {
+ target: { pageId: "certificates" },
+ title: "Certificates",
+ components: {
+ Icon: CertificateIcon,
+ }
+ },
+ ]
+}
+```
+
+Then we need to register `PageRegistration` object with `certificates` id and define `CertificatePage` component to render certificates.
+
+```typescript
+export default class CrdSampleExtension extends LensRendererExtension {
+ ...
+
+ clusterPages = [{
+ id: "certificates",
+ components: {
+ Page: () => ,
+ MenuIcon: CertificateIcon,
+ }
+ }]
+}
+```
+
+## List Certificate Objects on the Cluster Page
+
+In the previous step we defined `CertificatePage` component to render certificates.
+In this step we will actually implement that.
+`CertificatePage` is a React component that will render `Component.KubeObjectListLayout` component to list `Certificate` CRD objects.
+
+### Get CRD objects
+
+In order to list CRD objects, we need first fetch those from Kubernetes API.
+Lens Extensions API provides easy mechanism to do this.
+We just need to define `Certificate` class derived from `K8sApi.KubeObject`, `CertificatesApi`derived from `K8sApi.KubeApi` and `CertificatesStore` derived from `K8sApi.KubeObjectStore`.
+
+`Certificate` class defines properties found in the CRD object:
+
+```typescript
+export class Certificate extends K8sApi.KubeObject {
+ static kind = "Certificate"
+ static namespaced = true
+ static apiBase = "/apis/cert-manager.io/v1alpha2/certificates"
+
+ kind: string
+ apiVersion: string
+ metadata: {
+ name: string;
+ namespace: string;
+ selfLink: string;
+ uid: string;
+ resourceVersion: string;
+ creationTimestamp: string;
+ labels: {
+ [key: string]: string;
+ };
+ annotations: {
+ [key: string]: string;
+ };
+ }
+ spec: {
+ dnsNames: string[];
+ issuerRef: {
+ group: string;
+ kind: string;
+ name: string;
+ }
+ secretName: string
+ }
+ status: {
+ conditions: {
+ lastTransitionTime: string;
+ message: string;
+ reason: string;
+ status: string;
+ type?: string;
+ }[];
+ }
+}
+```
+
+With `CertificatesApi` class we are able to manage `Certificate` objects in Kubernetes API:
+
+```typescript
+export class CertificatesApi extends K8sApi.KubeApi {
+}
+export const certificatesApi = new CertificatesApi({
+ objectConstructor: Certificate
+});
+```
+
+`CertificateStore` defines storage for `Certificate` objects
+
+```typescript
+export class CertificatesStore extends K8sApi.KubeObjectStore {
+ api = certificatesApi
+}
+
+export const certificatesStore = new CertificatesStore();
+```
+
+And, finally, we register this store to Lens's API manager.
+
+```typescript
+K8sApi.apiManager.registerStore(certificatesStore);
+```
+
+
+### Create CertificatePage component
+
+Now we have created mechanism to manage `Certificate` objects in Kubernetes API.
+Then we need to fetch those and render them in the UI.
+
+First we define `CertificatePage` class that extends `React.Component`.
+
+```typescript
+import { Component, LensRendererExtension } from "@k8slens/extensions";
+import React from "react";
+import { certificatesStore } from "../certificate-store";
+import { Certificate } from "../certificate"
+
+export class CertificatePage extends React.Component<{ extension: LensRendererExtension }> {
+
+}
+```
+
+Next we will implement `render` method that will display certificates in a list.
+To do that, we just need to add `Component.KubeObjectListLayout` component inside `Component.TabLayout` component in render method.
+To define which objects the list is showing, we need to pass `certificateStore` object to `Component.KubeObjectListLayout` in `store` property.
+`Component.KubeObjectListLayout` will fetch automatically items from the given store when component is mounted.
+Also, we can define needed sorting callbacks and search filters for the list:
+
+```typescript
+enum sortBy {
+ name = "name",
+ namespace = "namespace",
+ issuer = "issuer"
+}
+
+export class CertificatePage extends React.Component<{ extension: LensRendererExtension }> {
+ // ...
+
+ render() {
+ return (
+
+ certificate.getName(),
+ [sortBy.namespace]: (certificate: Certificate) => certificate.metadata.namespace,
+ [sortBy.issuer]: (certificate: Certificate) => certificate.spec.issuerRef.name
+ }}
+ searchFilters={[
+ (certificate: Certificate) => certificate.getSearchFields()
+ ]}
+ renderHeaderTitle="Certificates"
+ renderTableHeader={[
+ { title: "Name", className: "name", sortBy: sortBy.name },
+ { title: "Namespace", className: "namespace", sortBy: sortBy.namespace },
+ { title: "Issuer", className: "issuer", sortBy: sortBy.namespace },
+ ]}
+ renderTableContents={(certificate: Certificate) => [
+ certificate.getName(),
+ certificate.metadata.namespace,
+ certificate.spec.issuerRef.name
+ ]}
+ />
+
+ )
+ }
+}
+```
+
+### Customize Details panel
+
+We have learned now, how to list CRD objects in a list view.
+Next, we will learn how to customize details panel that will be opened when the object is clicked in the list.
+
+First, we need to register our custom component to render details for the specific Kubernetes custom resource, in our case `Certificate`.
+We will do this again in `CrdSampleExtension` class:
+
+```typescript
+export default class CrdSampleExtension extends LensRendererExtension {
+ //...
+
+ kubeObjectDetailItems = [{
+ kind: Certificate.kind,
+ apiVersions: ["cert-manager.io/v1alpha2"],
+ components: {
+ Details: (props: CertificateDetailsProps) =>
+ }
+ }]
+}
+```
+
+Here we defined that `CertificateDetails` component will render the resource details.
+So, next we need to implement that component.
+Lens will inject `Certificate` object into our component so we just need to render some information out of it.
+We can use `Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere:
+
+```typescript
+import { Component, K8sApi } from "@k8slens/extensions";
+import React from "react";
+import { Certificate } from "../certificate";
+
+export interface CertificateDetailsProps extends Component.KubeObjectDetailsProps{
+}
+
+export class CertificateDetails extends React.Component {
+
+ render() {
+ const { object: certificate } = this.props;
+ if (!certificate) return null;
+ return (
+
+ )
+ }
+}
+```
+
+## Summary
+
+Like we can see above, it's very easy to add custom pages and fetch Kubernetes resources by using Extensions API.
+Please see the [complete source code](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) to test it out.
diff --git a/docs/extensions/guides/main-extension.md b/docs/extensions/guides/main-extension.md
new file mode 100644
index 0000000000..f1212c0d37
--- /dev/null
+++ b/docs/extensions/guides/main-extension.md
@@ -0,0 +1,105 @@
+# Main Extension
+
+The Main Extension API is the interface to Lens's main process.
+Lens runs in both main and renderer processes.
+The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items, and run custom code in Lens's main process.
+
+## `LensMainExtension` Class
+
+### `onActivate()` and `onDeactivate()` Methods
+
+To create a main extension simply extend the `LensMainExtension` class:
+
+```typescript
+import { LensMainExtension } from "@k8slens/extensions";
+
+export default class ExampleExtensionMain extends LensMainExtension {
+ onActivate() {
+ console.log('custom main process extension code started');
+ }
+
+ onDeactivate() {
+ console.log('custom main process extension de-activated');
+ }
+}
+```
+
+Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`.
+Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`.
+You can initiate custom code by implementing `onActivate()`.
+Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
+
+Disable extensions from the Lens Extensions page:
+
+1. Navigate to **File** > **Extensions** in the top menu bar.
+(On Mac, it is **Lens** > **Extensions**.)
+2. Click **Disable** on the extension you want to disable.
+
+The example above logs messages when the extension is enabled and disabled.
+To see standard output from the main process there must be a console connected to it.
+Achieve this by starting Lens from the command prompt.
+
+The following example is a little more interesting.
+It accesses some Lens state data, and it periodically logs the name of the cluster that is currently active in Lens.
+
+```typescript
+import { LensMainExtension, Store } from "@k8slens/extensions";
+
+export default class ActiveClusterExtensionMain extends LensMainExtension {
+
+ timer: NodeJS.Timeout
+
+ onActivate() {
+ console.log("Cluster logger activated");
+ this.timer = setInterval(() => {
+ if (!Store.ClusterStore.getInstance().active) {
+ console.log("No active cluster");
+ return;
+ }
+ console.log("active cluster is", Store.ClusterStore.getInstance().active.contextName)
+ }, 5000)
+ }
+
+ onDeactivate() {
+ clearInterval(this.timer)
+ console.log("Cluster logger deactivated");
+ }
+}
+```
+
+For more details on accessing Lens state data, please see the [Stores](../stores) guide.
+
+### `appMenus`
+
+The Main Extension API allows you to customize the UI application menu.
+Note that this is the only UI feature that the Main Extension API allows you to customize.
+The following example demonstrates adding an item to the **Help** menu.
+
+``` typescript
+import { LensMainExtension } from "@k8slens/extensions";
+
+export default class SamplePageMainExtension extends LensMainExtension {
+ appMenus = [
+ {
+ parentId: "help",
+ label: "Sample",
+ click() {
+ console.log("Sample clicked");
+ }
+ }
+ ]
+}
+```
+
+`appMenus` is an array of objects that satisfy the `MenuRegistration` interface.
+`MenuRegistration` extends React's `MenuItemConstructorOptions` interface.
+The properties of the appMenus array objects are defined as follows:
+
+* `parentId` is the name of the menu where your new menu item will be listed.
+Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`.
+`"lens"` is valid on Mac only.
+* `label` is the name of your menu item.
+* `click()` is called when the menu item is selected.
+In this example, we simply log a message.
+However, you would typically have this navigate to a specific page or perform another operation.
+Note that pages are associated with the [`LensRendererExtension`](renderer-extension.md) class and can be defined in the process of extending it.
diff --git a/docs/extensions/guides/protocol-handlers.md b/docs/extensions/guides/protocol-handlers.md
new file mode 100644
index 0000000000..8e13c8436a
--- /dev/null
+++ b/docs/extensions/guides/protocol-handlers.md
@@ -0,0 +1,83 @@
+# Lens Protocol Handlers
+
+Lens has a file association with the `lens://` protocol.
+This means that Lens can be opened by external programs by providing a link that has `lens` as its protocol.
+Lens provides a routing mechanism that extensions can use to register custom handlers.
+
+## Registering A Protocol Handler
+
+The field `protocolHandlers` exists both on [`LensMainExtension`](extensions/api/classes/lensmainextension/#protocolhandlers) and on [`LensRendererExtension`](extensions/api/classes/lensrendererextension/#protocolhandlers).
+This field will be iterated through every time a `lens://` request gets sent to the application.
+The `pathSchema` argument must comply with the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) package's `compileToRegex` function.
+
+Once you have registered a handler it will be called when a user opens a link on their computer.
+Handlers will be run in both `main` and `renderer` in parallel with no synchronization between the two processes.
+Furthermore, both `main` and `renderer` are routed separately.
+In other words, which handler is selected in either process is independent from the list of possible handlers in the other.
+
+Example of registering a handler:
+
+```typescript
+import { LensMainExtension, Interface } from "@k8slens/extensions";
+
+function rootHandler(params: Iterface.ProtocolRouteParams) {
+ console.log("routed to ExampleExtension", params);
+}
+
+export default class ExampleExtensionMain extends LensMainExtension {
+ protocolHandlers = [
+ pathSchema: "/",
+ handler: rootHandler,
+ ]
+}
+```
+
+For testing the routing of URIs the `open` (on macOS) or `xdg-open` (on most linux) CLI utilities can be used.
+For the above handler, the following URI would be always routed to it:
+
+```
+open lens://extension/example-extension/
+```
+
+## Deregistering A Protocol Handler
+
+All that is needed to deregister a handler is to remove it from the array of handlers.
+
+## Routing Algorithm
+
+The routing mechanism for extensions is quite straight forward.
+For example consider an extension `example-extension` which is published by the `@mirantis` org.
+If it were to register a handler with `"/display/:type"` as its corresponding link then we would match the following URI like this:
+
+
+
+Once matched, the handler would be called with the following argument (note both `"search"` and `"pathname"` will always be defined):
+
+```json
+{
+ "search": {
+ "text": "Hello"
+ },
+ "pathname": {
+ "type": "notification"
+ }
+}
+```
+
+As the diagram above shows, the search (or query) params are not considered as part of the handler resolution.
+If the URI had instead been `lens://extension/@mirantis/example-extension/display/notification/green` then a third (and optional) field will have the rest of the path.
+The `tail` field would be filled with `"/green"`.
+If multiple `pathSchema`'s match a given URI then the most specific handler will be called.
+
+For example consider the following `pathSchema`'s:
+
+1. `"/"`
+1. `"/display"`
+1. `"/display/:type"`
+1. `"/show/:id"`
+
+The URI sub-path `"/display"` would be routed to #2 since it is an exact match.
+On the other hand, the subpath `"/display/notification"` would be routed to #3.
+
+The URI is routed to the most specific matching `pathSchema`.
+This way the `"/"` (root) `pathSchema` acts as a sort of catch all or default route if no other route matches.
diff --git a/docs/extensions/guides/renderer-extension.md b/docs/extensions/guides/renderer-extension.md
new file mode 100644
index 0000000000..1c2512023a
--- /dev/null
+++ b/docs/extensions/guides/renderer-extension.md
@@ -0,0 +1,918 @@
+# Renderer Extension
+
+The Renderer Extension API is the interface to Lens's renderer process.
+Lens runs in both the main and renderer processes.
+The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, and run custom code in Lens's renderer process.
+
+The custom Lens UI elements that you can add include:
+
+* [Cluster pages](#clusterpages)
+* [Cluster page menus](#clusterpagemenus)
+* [Global pages](#globalpages)
+* [Global page menus](#globalpagemenus)
+* [Cluster features](#clusterfeatures)
+* [App preferences](#apppreferences)
+* [Status bar items](#statusbaritems)
+* [KubeObject menu items](#kubeobjectmenuitems)
+* [KubeObject detail items](#kubeobjectdetailitems)
+
+All UI elements are based on React components.
+
+## `LensRendererExtension` Class
+
+### `onActivate()` and `onDeactivate()` Methods
+
+To create a renderer extension, extend the `LensRendererExtension` class:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+
+export default class ExampleExtensionMain extends LensRendererExtension {
+ onActivate() {
+ console.log('custom renderer process extension code started');
+ }
+
+ onDeactivate() {
+ console.log('custom renderer process extension de-activated');
+ }
+}
+```
+
+Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`.
+Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`.
+You can initiate custom code by implementing `onActivate()`.
+Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
+
+!!! info
+ Disable extensions from the Lens Extensions page:
+
+ 1. Navigate to **File** > **Extensions** in the top menu bar.
+ (On Mac, it is **Lens** > **Extensions**.)
+ 2. Click **Disable** on the extension you want to disable.
+
+The example above logs messages when the extension is enabled and disabled.
+
+### `clusterPages`
+
+Cluster pages appear in the cluster dashboard.
+Use cluster pages to display information about or add functionality to the active cluster.
+It is also possible to include custom details from other clusters.
+Use your extension to access Kubernetes resources in the active cluster with [`ClusterStore.getInstance()`](../stores#Clusterstore).
+
+Add a cluster page definition to a `LensRendererExtension` subclass with the following example:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExampleIcon, ExamplePage } from "./page"
+import React from "react"
+
+export default class ExampleExtension extends LensRendererExtension {
+ clusterPages = [
+ {
+ id: "hello",
+ components: {
+ Page: () => ,
+ }
+ }
+ ];
+}
+```
+
+`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
+The properties of the `clusterPages` array objects are defined as follows:
+
+* `id` is a string that identifies the page.
+* `components` matches the `PageComponents` interface for which there is one field, `Page`.
+* `Page` is of type ` React.ComponentType`.
+It offers flexibility in defining the appearance and behavior of your page.
+
+`ExamplePage` in the example above can be defined in `page.tsx`:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import React from "react"
+
+export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
+ render() {
+ return (
+
+
Hello world!
+
+ )
+ }
+}
+```
+
+Note that the `ExamplePage` class defines the `extension` property.
+This allows the `ExampleExtension` object to be passed in the cluster page definition in the React style.
+This way, `ExamplePage` can access all `ExampleExtension` subclass data.
+
+The above example shows how to create a cluster page, but not how to make that page available to the Lens user.
+Use `clusterPageMenus`, covered in the next section, to add cluster pages to the Lens UI.
+
+### `clusterPageMenus`
+
+`clusterPageMenus` allows you to add cluster page menu items to the secondary left nav.
+
+By expanding on the above example, you can add a cluster page menu item to the `ExampleExtension` definition:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExampleIcon, ExamplePage } from "./page"
+import React from "react"
+
+export default class ExampleExtension extends LensRendererExtension {
+ clusterPages = [
+ {
+ id: "hello",
+ components: {
+ Page: () => ,
+ }
+ }
+ ];
+
+ clusterPageMenus = [
+ {
+ target: { pageId: "hello" },
+ title: "Hello World",
+ components: {
+ Icon: ExampleIcon,
+ }
+ },
+ ];
+}
+```
+
+`clusterPageMenus` is an array of objects that satisfy the `ClusterPageMenuRegistration` interface.
+This element defines how the cluster page menu item will appear and what it will do when you click it.
+The properties of the `clusterPageMenus` array objects are defined as follows:
+
+* `target` links to the relevant cluster page using `pageId`.
+* `pageId` takes the value of the relevant cluster page's `id` property.
+* `title` sets the name of the cluster page menu item that will appear in the left side menu.
+* `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
+
+The above example creates a menu item that reads **Hello World**.
+When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
+
+This example requires the definition of another React-based component, `ExampleIcon`, which has been added to `page.tsx`, as follows:
+
+```typescript
+import { LensRendererExtension, Component } from "@k8slens/extensions";
+import React from "react"
+
+export function ExampleIcon(props: Component.IconProps) {
+ return
+}
+
+export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
+ render() {
+ return (
+
+
Hello world!
+
+ )
+ }
+}
+```
+
+Lens includes various built-in components available for extension developers to use.
+One of these is the `Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
+The properties that `Component.Icon` uses are defined as follows:
+
+* `material` takes the name of the icon you want to use.
+* `tooltip` sets the text you want to appear when a user hovers over the icon.
+
+`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages.
+The following example groups two sub menu items under one parent menu item:
+
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExampleIcon, ExamplePage } from "./page"
+import React from "react"
+
+export default class ExampleExtension extends LensRendererExtension {
+ clusterPages = [
+ {
+ id: "hello",
+ components: {
+ Page: () => ,
+ }
+ },
+ {
+ id: "bonjour",
+ components: {
+ Page: () => ,
+ }
+ }
+ ];
+
+ clusterPageMenus = [
+ {
+ id: "example",
+ title: "Greetings",
+ components: {
+ Icon: ExampleIcon,
+ }
+ },
+ {
+ parentId: "example",
+ target: { pageId: "hello" },
+ title: "Hello World",
+ components: {
+ Icon: ExampleIcon,
+ }
+ },
+ {
+ parentId: "example",
+ target: { pageId: "bonjour" },
+ title: "Bonjour le monde",
+ components: {
+ Icon: ExempleIcon,
+ }
+ }
+ ];
+}
+```
+
+The above defines two cluster pages and three cluster page menu objects.
+The cluster page definitions are straightforward.
+The three cluster page menu objects include one parent menu item and two sub menu items.
+The first cluster page menu object defines the parent of a foldout submenu.
+Setting the `id` field in a cluster page menu definition implies that it is defining a foldout submenu.
+Also note that the `target` field is not specified (it is ignored if the `id` field is specified).
+This cluster page menu object specifies the `title` and `components` fields, which are used in displaying the menu item in the cluster dashboard sidebar.
+Initially the submenu is hidden.
+Activating this menu item toggles on and off the appearance of the submenu below it.
+The remaining two cluster page menu objects define the contents of the submenu.
+A cluster page menu object is defined to be a submenu item by setting the `parentId` field to the id of the parent of a foldout submenu, `"example"` in this case.
+
+This is what the example will look like, including how the menu item will appear in the secondary left nav:
+
+### `globalPages`
+
+Global pages are independent of the cluster dashboard and can fill the entire Lens UI.
+Their primary use is to display information and provide functionality across clusters, including customized data and functionality unique to your extension.
+
+Typically, you would use a [global page menu](#globalpagemenus) located in the left nav to trigger a global page.
+You can also trigger a global page with a [custom app menu selection](../main-extension#appmenus) from a Main Extension or a [custom status bar item](#statusbaritems).
+Unlike cluster pages, users can trigger global pages even when there is no active cluster.
+
+The following example defines a `LensRendererExtension` subclass with a single global page definition:
+
+```typescript
+import { LensRendererExtension } from '@k8slens/extensions';
+import { HelpPage } from './page';
+import React from 'react';
+
+export default class HelpExtension extends LensRendererExtension {
+ globalPages = [
+ {
+ id: "help",
+ components: {
+ Page: () => ,
+ }
+ }
+ ];
+}
+```
+
+`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
+The properties of the `globalPages` array objects are defined as follows:
+
+* `id` is a string that identifies the page.
+* `components` matches the `PageComponents` interface for which there is one field, `Page`.
+* `Page` is of type `React.ComponentType`.
+It offers flexibility in defining the appearance and behavior of your page.
+
+`HelpPage` in the example above can be defined in `page.tsx`:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import React from "react"
+
+export class HelpPage extends React.Component<{ extension: LensRendererExtension }> {
+ render() {
+ return (
+
+
Help yourself
+
+ )
+ }
+}
+```
+
+Note that the `HelpPage` class defines the `extension` property.
+This allows the `HelpExtension` object to be passed in the global page definition in the React-style.
+This way, `HelpPage` can access all `HelpExtension` subclass data.
+
+This example code shows how to create a global page, but not how to make that page available to the Lens user.
+Global pages can be made available in the following ways:
+
+* To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
+* To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems).
+* To add global pages to the left side menu, see [`globalPageMenus`](#globalpagemenus).
+
+### `globalPageMenus`
+
+`globalPageMenus` allows you to add global page menu items to the left nav.
+
+By expanding on the above example, you can add a global page menu item to the `HelpExtension` definition:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { HelpIcon, HelpPage } from "./page"
+import React from "react"
+
+export default class HelpExtension extends LensRendererExtension {
+ globalPages = [
+ {
+ id: "help",
+ components: {
+ Page: () => ,
+ }
+ }
+ ];
+
+ globalPageMenus = [
+ {
+ target: { pageId: "help" },
+ title: "Help",
+ components: {
+ Icon: HelpIcon,
+ }
+ },
+ ];
+}
+```
+
+`globalPageMenus` is an array of objects that satisfy the `PageMenuRegistration` interface.
+This element defines how the global page menu item will appear and what it will do when you click it.
+The properties of the `globalPageMenus` array objects are defined as follows:
+
+* `target` links to the relevant global page using `pageId`.
+* `pageId` takes the value of the relevant global page's `id` property.
+* `title` sets the name of the global page menu item that will display as a tooltip in the left nav.
+* `components` is used to set an icon that appears in the left nav.
+
+The above example creates a "Help" icon menu item.
+When users click the icon, the Lens UI will display the contents of `ExamplePage`.
+
+This example requires the definition of another React-based component, `HelpIcon`.
+Update `page.tsx` from the example above with the `HelpIcon` definition, as follows:
+
+```typescript
+import { LensRendererExtension, Component } from "@k8slens/extensions";
+import React from "react"
+
+export function HelpIcon(props: Component.IconProps) {
+ return
+}
+
+export class HelpPage extends React.Component<{ extension: LensRendererExtension }> {
+ render() {
+ return (
+
+
Help
+
+ )
+ }
+}
+```
+
+Lens includes various built-in components available for extension developers to use.
+One of these is the `Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
+The property that `Component.Icon` uses is defined as follows:
+
+* `material` takes the name of the icon you want to use.
+
+This is what the example will look like, including how the menu item will appear in the left nav:
+
+
+
+### `clusterFeatures`
+
+Cluster features are Kubernetes resources that can be applied to and managed within the active cluster.
+They can be installed and uninstalled by the Lens user from the cluster **Settings** page.
+
+!!! info
+ To access the cluster **Settings** page, right-click the relevant cluster in the left side menu and click **Settings**.
+
+The following example shows how to add a cluster feature as part of a `LensRendererExtension`:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions"
+import { ExampleFeature } from "./src/example-feature"
+import React from "react"
+
+export default class ExampleFeatureExtension extends LensRendererExtension {
+ clusterFeatures = [
+ {
+ title: "Example Feature",
+ components: {
+ Description: () => {
+ return (
+
+ Enable an example feature.
+
+ )
+ }
+ },
+ feature: new ExampleFeature()
+ }
+ ];
+}
+```
+
+The properties of the `clusterFeatures` array objects are defined as follows:
+
+* `title` and `components.Description` provide content that appears on the cluster settings page, in the **Features** section.
+* `feature` specifies an instance which extends the abstract class `ClusterFeature.Feature`, and specifically implements the following methods:
+
+```typescript
+ abstract install(cluster: Cluster): Promise;
+ abstract upgrade(cluster: Cluster): Promise;
+ abstract uninstall(cluster: Cluster): Promise;
+ abstract updateStatus(cluster: Cluster): Promise;
+```
+
+The four methods listed above are defined as follows:
+
+* The `install()` method installs Kubernetes resources using the `applyResources()` method, or by directly accessing the [Kubernetes API](../../api/lens/extensions/README.md).
+This method is typically called when a user indicates that they want to install the feature (i.e., by clicking **Install** for the feature in the cluster settings page).
+
+* The `upgrade()` method upgrades the Kubernetes resources already installed, if they are relevant to the feature.
+This method is typically called when a user indicates that they want to upgrade the feature (i.e., by clicking **Upgrade** for the feature in the cluster settings page).
+
+* The `uninstall()` method uninstalls Kubernetes resources using the [Kubernetes API](../../api/lens/extensions/README.md).
+This method is typically called when a user indicates that they want to uninstall the feature (i.e., by clicking **Uninstall** for the feature in the cluster settings page).
+
+* The `updateStatus()` method provides the current status information in the `status` field of the `ClusterFeature.Feature` parent class.
+Lens periodically calls this method to determine details about the feature's current status.
+The implementation of this method should uninstall Kubernetes resources using the Kubernetes api (`K8sApi`)
+Consider using the following properties with `updateStatus()`:
+
+ * `status.currentVersion` and `status.latestVersion` may be displayed by Lens in the feature's description.
+
+ * `status.installed` should be set to `true` if the feature is installed, and `false` otherwise.
+
+ * `status.canUpgrade` is set according to a rule meant to determine whether the feature can be upgraded.
+ This rule can involve `status.currentVersion` and `status.latestVersion`, if desired.
+
+The following shows a very simple implementation of a `ClusterFeature`:
+
+```typescript
+import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions";
+import * as path from "path";
+
+export class ExampleFeature extends ClusterFeature.Feature {
+
+ async install(cluster: Store.Cluster): Promise {
+
+ super.applyResources(cluster, path.join(__dirname, "../resources/"));
+ }
+
+ async upgrade(cluster: Store.Cluster): Promise {
+ return this.install(cluster);
+ }
+
+ async updateStatus(cluster: Store.Cluster): Promise {
+ try {
+ const pod = K8sApi.forCluster(cluster, K8sApi.Pod);
+ const examplePod = await pod.get({name: "example-pod", namespace: "default"});
+ if (examplePod?.kind) {
+ this.status.installed = true;
+ this.status.currentVersion = examplePod.spec.containers[0].image.split(":")[1];
+ this.status.canUpgrade = true; // a real implementation would perform a check here that is relevant to the specific feature
+ } else {
+ this.status.installed = false;
+ this.status.canUpgrade = false;
+ }
+ } catch(e) {
+ if (e?.error?.code === 404) {
+ this.status.installed = false;
+ this.status.canUpgrade = false;
+ }
+ }
+
+ return this.status;
+ }
+
+ async uninstall(cluster: Store.Cluster): Promise {
+ const podApi = K8sApi.forCluster(cluster, K8sApi.Pod);
+ await podApi.delete({name: "example-pod", namespace: "default"});
+ }
+}
+```
+
+This example implements the `install()` method by invoking the helper `applyResources()` method.
+`applyResources()` tries to apply all resources read from all files found in the folder path provided.
+In this case the folder path is the `../resources` subfolder relative to the current source code's folder.
+The file `../resources/example-pod.yml` could contain:
+
+``` yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: example-pod
+spec:
+ containers:
+ - name: example-pod
+ image: nginx
+```
+
+The example above implements the four methods as follows:
+
+* It implements `upgrade()` by invoking the `install()` method.
+Depending on the feature to be supported by an extension, upgrading may require additional and/or different steps.
+
+* It implements `uninstall()` by utilizing the [Kubernetes API](../../api/lens/extensions/README.md) which Lens provides to delete the `example-pod` applied by the `install()` method.
+
+* It implements `updateStatus()` by using the [Kubernetes API](../../api/lens/extensions/README.md) which Lens provides to determine whether the `example-pod` is installed, what version is associated with it, and whether it can be upgraded.
+The implementation determines what the status is for a specific cluster feature.
+
+### `appPreferences`
+
+The Lens **Preferences** page is a built-in global page.
+You can use Lens extensions to add custom preferences to the Preferences page, providing a single location for users to configure global options.
+
+The following example demonstrates adding a custom preference:
+
+```typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
+import { observable } from "mobx";
+import React from "react";
+
+export default class ExampleRendererExtension extends LensRendererExtension {
+
+ @observable preference = { enabled: false };
+
+ appPreferences = [
+ {
+ title: "Example Preferences",
+ components: {
+ Input: () => ,
+ Hint: () =>
+ }
+ }
+ ];
+}
+```
+
+`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
+The properties of the `appPreferences` array objects are defined as follows:
+
+* `title` sets the heading text displayed on the Preferences page.
+* `components` specifies two `React.Component` objects that define the interface for the preference.
+ * `Input` specifies an interactive input element for the preference.
+ * `Hint` provides descriptive information for the preference, shown below the `Input` element.
+
+!!! note
+ Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
+
+`ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance.
+This is how `ExampleRendererExtension` handles the state of the preference input.
+`ExampleRendererExtension` has a `preference` field, which you will add to `ExamplePreferenceInput`.
+
+In this example `ExamplePreferenceInput`, `ExamplePreferenceHint`, and `ExamplePreferenceProps` are defined in `./src/example-preference.tsx` as follows:
+
+```typescript
+import { Component } from "@k8slens/extensions";
+import { observer } from "mobx-react";
+import React from "react";
+
+export class ExamplePreferenceProps {
+ preference: {
+ enabled: boolean;
+ }
+}
+
+@observer
+export class ExamplePreferenceInput extends React.Component {
+
+ render() {
+ const { preference } = this.props;
+ return (
+ { preference.enabled = v; }}
+ />
+ );
+ }
+}
+
+export class ExamplePreferenceHint extends React.Component {
+ render() {
+ return (
+ This is an example of an appPreference for extensions.
+ );
+ }
+}
+```
+
+`ExamplePreferenceInput` implements a simple checkbox using Lens's `Component.Checkbox` using the following properties:
+
+* `label` sets the text that displays next to the checkbox.
+* `value` is initially set to `preference.enabled`.
+* `onChange` is a function that responds when the state of the checkbox changes.
+
+`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
+This is an object with the single `enabled` property.
+It is used to indicate the state of the preference, and it is bound to the checkbox state in `onChange`.
+
+`ExamplePreferenceHint` is a simple text span.
+
+The above example introduces the decorators `observable` and `observer` from the [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) packages.
+`mobx` simplifies state management.
+Without it, this example would not visually update the checkbox properly when the user activates it.
+[Lens uses `mobx`](../working-with-mobx) extensively for state management of its own UI elements.
+We recommend that extensions rely on it, as well.
+Alternatively, you can use React's state management, though `mobx` is typically simpler to use.
+
+Note that you can manage an extension's state data using an `ExtensionStore` object, which conveniently handles persistence and synchronization.
+To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state.
+However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore).
+
+### `statusBarItems`
+
+The status bar is the blue strip along the bottom of the Lens UI.
+`statusBarItems` are `React.ReactNode` types.
+They can be used to display status information, or act as links to global pages as well as external pages.
+
+The following example adds a `statusBarItems` definition and a `globalPages` definition to a `LensRendererExtension` subclass.
+It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
+
+```typescript
+import { LensRendererExtension } from '@k8slens/extensions';
+import { HelpIcon, HelpPage } from "./page"
+import React from 'react';
+
+export default class HelpExtension extends LensRendererExtension {
+ globalPages = [
+ {
+ id: "help",
+ components: {
+ Page: () => ,
+ }
+ }
+ ];
+
+ statusBarItems = [
+ {
+ components: {
+ Item: (
+
this.navigate("help")}
+ >
+
+ My Status Bar Item
+
+ )
+ },
+ },
+ ];
+}
+```
+
+The properties of the `statusBarItems` array objects are defined as follows:
+
+* `Item` specifies the `React.Component` that will be shown on the status bar.
+By default, items are added starting from the right side of the status bar.
+Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text.
+The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
+* `onClick` determines what the `statusBarItem` does when it is clicked.
+In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method.
+`navigate` takes the `id` of the associated global page as a parameter.
+Thus, clicking the status bar item activates the associated global pages.
+
+### `kubeObjectMenuItems`
+
+An extension can add custom menu items (`kubeObjectMenuItems`) for specific Kubernetes resource kinds and apiVersions.
+`kubeObjectMenuItems` appear under the vertical ellipsis for each listed resource in the cluster dashboard:
+
+
+
+They also appear on the title bar of the details page for specific resources:
+
+
+
+The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions";
+import { NamespaceMenuItem } from "./src/namespace-menu-item"
+
+export default class ExampleExtension extends LensRendererExtension {
+ kubeObjectMenuItems = [
+ {
+ kind: "Namespace",
+ apiVersions: ["v1"],
+ components: {
+ MenuItem: (props: Component.KubeObjectMenuProps) =>
+ }
+ }
+ ];
+}
+
+```
+
+`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
+The example above adds a menu item for namespaces in the cluster dashboard.
+The properties of the `kubeObjectMenuItems` array objects are defined as follows:
+
+* `kind` specifies the Kubernetes resource type the menu item will apply to.
+* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
+* `components` defines the menu item's appearance and behavior.
+* `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
+In this example a `NamespaceMenuItem` object is returned.
+
+`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
+
+```typescript
+import React from "react";
+import { Component, K8sApi, Navigation} from "@k8slens/extensions";
+
+export function NamespaceMenuItem(props: Component.KubeObjectMenuProps) {
+ const { object: namespace, toolbar } = props;
+ if (!namespace) return null;
+
+ const namespaceName = namespace.getName();
+
+ const sendToTerminal = (command: string) => {
+ Component.terminalStore.sendCommand(command, {
+ enter: true,
+ newTab: true,
+ });
+ Navigation.hideDetails();
+ };
+
+ const getPods = () => {
+ sendToTerminal(`kubectl get pods -n ${namespaceName}`);
+ };
+
+ return (
+
+
+ Get Pods
+
+ );
+}
+
+```
+
+`NamespaceMenuItem` returns a `Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
+In the example, `getPods()` opens a terminal tab and runs `kubectl` to get a list of pods running in the current namespace.
+
+The name of the namespace is retrieved from `props` passed into `NamespaceMenuItem()`.
+`namespace` is the `props.object`, which is of type `K8sApi.Namespace`.
+`K8sApi.Namespace` is the API for accessing namespaces.
+The current namespace in this example is simply given by `namespace.getName()`.
+Thus, `kubeObjectMenuItems` afford convenient access to the specific resource selected by the user.
+
+### `kubeObjectDetailItems`
+
+An extension can add custom details (`kubeObjectDetailItems`) for specified Kubernetes resource kinds and apiVersions.
+These custom details appear on the details page for a specific resource, such as a Namespace as shown here:
+
+
+
+The following example shows how to use `kubeObjectDetailItems` to add a tabulated list of pods to the Namespace resource details page:
+
+```typescript
+import React from "react"
+import { LensRendererExtension } from "@k8slens/extensions";
+import { NamespaceDetailsItem } from "./src/namespace-details-item"
+
+export default class ExampleExtension extends LensRendererExtension {
+ kubeObjectDetailItems = [
+ {
+ kind: "Namespace",
+ apiVersions: ["v1"],
+ priority: 10,
+ components: {
+ Details: (props: Component.KubeObjectDetailsProps) =>
+ }
+ }
+ ];
+}
+```
+
+`kubeObjectDetailItems` is an array of objects matching the `KubeObjectDetailRegistration` interface.
+This example above adds a detail item for namespaces in the cluster dashboard.
+The properties of the `kubeObjectDetailItems` array objects are defined as follows:
+
+* `kind` specifies the Kubernetes resource type the detail item will apply to.
+* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
+* `components` defines the detail item's appearance and behavior.
+* `Details` provides a function that returns a `React.Component` given a set of detail item properties.
+In this example a `NamespaceDetailsItem` object is returned.
+
+`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
+
+``` typescript
+import { Component, K8sApi } from "@k8slens/extensions";
+import { PodsDetailsList } from "./pods-details-list";
+import React from "react";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+
+@observer
+export class NamespaceDetailsItem extends React.Component> {
+
+ @observable private pods: K8sApi.Pod[];
+
+ async componentDidMount() {
+ this.pods = await K8sApi.podsApi.list({namespace: this.props.object.getName()});
+ }
+
+ render() {
+ return (
+
+
+
+
+ )
+ }
+}
+```
+
+Since `NamespaceDetailsItem` extends `React.Component>`, it can access the current namespace object (type `K8sApi.Namespace`) through `this.props.object`.
+You can query this object for many details about the current namespace.
+In the example above, `componentDidMount()` gets the namespace's name using the `K8sApi.Namespace` `getName()` method.
+Use the namespace's name to limit the list of pods only to those in the relevant namespace.
+To get this list of pods, this example uses the Kubernetes pods API `K8sApi.podsApi.list()` method.
+The `K8sApi.podsApi` is automatically configured for the active cluster.
+
+Note that `K8sApi.podsApi.list()` is an asynchronous method.
+Getting the pods list should occur prior to rendering the `NamespaceDetailsItem`.
+It is a common technique in React development to await async calls in `componentDidMount()`.
+However, `componentDidMount()` is called right after the first call to `render()`.
+In order to effect a subsequent `render()` call, React must be made aware of a state change.
+Like in the [`appPreferences` guide](#apppreferences), [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) are used to ensure `NamespaceDetailsItem` renders when the pods list updates.
+This is done simply by marking the `pods` field as an `observable` and the `NamespaceDetailsItem` class itself as an `observer`.
+
+Finally, the `NamespaceDetailsItem` renders using the `render()` method.
+Details are placed in drawers, and using `Component.DrawerTitle` provides a separator from details above this one.
+Multiple details in a drawer can be placed in `` elements for further separation, if desired.
+The rest of this example's details are defined in `PodsDetailsList`, found in `./pods-details-list.tsx`:
+
+``` typescript
+import React from "react";
+import { Component, K8sApi } from "@k8slens/extensions";
+
+interface Props {
+ pods: K8sApi.Pod[];
+}
+
+export class PodsDetailsList extends React.Component {
+
+ getTableRow(index: number) {
+ const {pods} = this.props;
+ return (
+
+ {pods[index].getName()}
+ {pods[index].getAge()}
+ {pods[index].getStatus()}
+
+ )
+ }
+
+ render() {
+ const {pods} = this.props
+ if (!pods?.length) {
+ return null;
+ }
+
+ return (
+
+
+
+ Name
+ Age
+ Status
+
+ {
+ pods.map((pod, index) => this.getTableRow(index))
+ }
+
+
+ )
+ }
+}
+```
+
+`PodsDetailsList` produces a simple table showing a list of the pods found in this namespace:
+
+
+
+Obtain the name, age, and status for each pod using the `K8sApi.Pod` methods.
+Construct the table using the `Component.Table` and related elements.
+
+For each pod the name, age, and status are obtained using the `K8sApi.Pod` methods.
+The table is constructed using the `Component.Table` and related elements.
+See [`Component` documentation](https://docs.k8slens.dev/master/extensions/api/modules/_renderer_api_components_/) for further details.
diff --git a/docs/extensions/guides/stores.md b/docs/extensions/guides/stores.md
new file mode 100644
index 0000000000..c8a5ec270d
--- /dev/null
+++ b/docs/extensions/guides/stores.md
@@ -0,0 +1,165 @@
+# Stores
+
+Stores are components that persist and synchronize state data. Lens uses a number of stores to maintain various kinds of state information, including:
+
+* The `ClusterStore` manages cluster state data (such as cluster details), and it tracks which cluster is active.
+* The `WorkspaceStore` manages workspace state data (such as the workspace name), and and it tracks which clusters belong to a given workspace.
+* The `ExtensionStore` manages custom extension state data.
+
+This guide focuses on the `ExtensionStore`.
+
+## ExtensionStore
+
+Extension developers can create their own store for managing state data by extending the `ExtensionStore` class.
+This guide shows how to create a store for the [`appPreferences`](../renderer-extension#apppreferences) guide example, which demonstrates how to add a custom preference to the **Preferences** page.
+The preference is a simple boolean that indicates whether or not something is enabled.
+However, in the example, the enabled state is not stored anywhere, and it reverts to the default when Lens is restarted.
+
+`Store.ExtensionStore`'s child class will need to be created before being used.
+It is recommended to call the inherited static method `getInstanceOrCreate()` only in one place, generally within you extension's `onActivate()` method.
+It is also recommenced to delete the instance, using the inherited static method `resetInstance()`, in your extension's `onDeactivate()` method.
+Everywhere else in your code you should use the `getInstance()` static method.
+This is so that your data is kept up to date and not persisted longer than expected.
+
+The following example code creates a store for the `appPreferences` guide example:
+
+``` typescript
+import { Store } from "@k8slens/extensions";
+import { observable, toJS } from "mobx";
+
+export type ExamplePreferencesModel = {
+ enabled: boolean;
+};
+
+export class ExamplePreferencesStore extends Store.ExtensionStore {
+
+ @observable enabled = false;
+
+ private constructor() {
+ super({
+ configName: "example-preferences-store",
+ defaults: {
+ enabled: false
+ }
+ });
+ }
+
+ protected fromStore({ enabled }: ExamplePreferencesModel): void {
+ this.enabled = enabled;
+ }
+
+ toJSON(): ExamplePreferencesModel {
+ return toJS({
+ enabled: this.enabled
+ }, {
+ recurseEverything: true
+ });
+ }
+}
+```
+
+First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type.
+This has a single field, `enabled`, which represents the preference's state.
+`ExamplePreferencesStore` extends `Store.ExtensionStore`, which is based on the `ExamplePreferencesModel`.
+The `enabled` field is added to the `ExamplePreferencesStore` class to hold the "live" or current state of the preference.
+Note the use of the `observable` decorator on the `enabled` field.
+The [`appPreferences`](../renderer-extension#apppreferences) guide example uses [MobX](https://mobx.js.org/README.html) for the UI state management, ensuring the checkbox updates when it's activated by the user.
+
+Next, our example implements the constructor and two abstract methods.
+The constructor specifies the name of the store (`"example-preferences-store"`) and the default (initial) value for the preference state (`enabled: false`).
+Lens internals call the `fromStore()` method when the store loads.
+It gives the extension the opportunity to retrieve the stored state data values based on the defined data model.
+The `enabled` field of the `ExamplePreferencesStore` is set to the value from the store whenever `fromStore()` is invoked.
+The `toJSON()` method is complementary to `fromStore()`.
+It is called when the store is being saved.
+`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
+The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
+
+Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension.
+Note that `ExamplePreferencesStore` is a singleton.
+Calling this function will create an instance if one has not been made before.
+Through normal use you should call `ExamplePreferencesStore.getInstance()` as that will throw an error if an instance does not exist.
+This provides some logical safety in that it limits where a new instance can be created.
+Thus it prevents an instance from being created when the constructor args are not present at the call site.
+
+If you are doing some cleanup it is recommended to call `ExamplePreferencesStore.getInstance(false)` which returns `undefined` instead of throwing when there is no instance.
+
+The following example code, modified from the [`appPreferences`](../renderer-extension#apppreferences) guide demonstrates how to use the extension store.
+`ExamplePreferencesStore` must be loaded in the main process, where loaded stores are automatically saved when exiting Lens.
+This can be done in `./main.ts`:
+
+``` typescript
+import { LensMainExtension } from "@k8slens/extensions";
+import { ExamplePreferencesStore } from "./src/example-preference-store";
+
+export default class ExampleMainExtension extends LensMainExtension {
+ async onActivate() {
+ await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
+ }
+}
+```
+
+Here, `ExamplePreferencesStore` loads with `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)`, which is conveniently called from the `onActivate()` method of `ExampleMainExtension`.
+Similarly, `ExamplePreferencesStore` must load in the renderer process where the `appPreferences` are handled.
+This can be done in `./renderer.ts`:
+
+``` typescript
+import { LensRendererExtension } from "@k8slens/extensions";
+import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
+import { ExamplePreferencesStore } from "./src/example-preference-store";
+import React from "react";
+
+export default class ExampleRendererExtension extends LensRendererExtension {
+
+ async onActivate() {
+ await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
+ }
+
+ appPreferences = [
+ {
+ title: "Example Preferences",
+ components: {
+ Input: () => ,
+ Hint: () =>
+ }
+ }
+ ];
+}
+```
+
+Again, `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)` is called to load `ExamplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`.
+
+`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
+
+``` typescript
+import { Component } from "@k8slens/extensions";
+import { observer } from "mobx-react";
+import React from "react";
+import { ExamplePreferencesStore } from "./example-preference-store";
+
+@observer
+export class ExamplePreferenceInput extends React.Component {
+
+ render() {
+ return (
+ { ExamplePreferencesStore.getInstace().enabled = v; }}
+ />
+ );
+ }
+}
+
+export class ExamplePreferenceHint extends React.Component {
+ render() {
+ return (
+ This is an example of an appPreference for extensions.
+ );
+ }
+}
+```
+
+The only change here is that `ExamplePreferenceProps` defines its `preference` field as an `ExamplePreferencesStore` type.
+Everything else works as before, except that now the `enabled` state persists across Lens restarts because it is managed by the
+`ExamplePreferencesStore`.
diff --git a/docs/extensions/guides/working-with-mobx.md b/docs/extensions/guides/working-with-mobx.md
new file mode 100644
index 0000000000..41ddc487a6
--- /dev/null
+++ b/docs/extensions/guides/working-with-mobx.md
@@ -0,0 +1,26 @@
+# Working with MobX
+
+## Introduction
+
+Lens uses MobX on top of React's state management system.
+The result is a more declarative state management style, rather than React's native `setState` mechanism.
+
+You can review how React handles state management [here](https://reactjs.org/docs/faq-state.html).
+
+The following is a quick overview:
+
+* `React.Component` is generic with respect to both `props` and `state` (which default to the empty object type).
+* `props` should be considered read-only from the point of view of the component, and it is the mechanism for passing in arguments to a component.
+* `state` is a component's internal state, and can be read by accessing the super-class field `state`.
+* `state` **must** be updated using the `setState` parent method which merges the new data with the old state.
+* React does some optimizations around re-rendering components after quick successions of `setState` calls.
+
+## How MobX Works:
+
+MobX is a package that provides an abstraction over React's state management system. The three main concepts are:
+
+* `observable` is a marker for data stored in the component's `state`.
+* `action` is a function that modifies any `observable` data.
+* `computed` is a marker for data that is derived from `observable` data, but that is not actually stored. Think of this as computing `isEmpty` rather than an observable field called `count`.
+
+Further reading is available on the [MobX website](https://mobx.js.org/the-gist-of-mobx.html).
diff --git a/docs/extensions/testing-and-publishing/bundling.md b/docs/extensions/testing-and-publishing/bundling.md
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/extensions/testing-and-publishing/publishing.md b/docs/extensions/testing-and-publishing/publishing.md
new file mode 100644
index 0000000000..d8e7b8efad
--- /dev/null
+++ b/docs/extensions/testing-and-publishing/publishing.md
@@ -0,0 +1,46 @@
+# Publishing Extensions
+
+To be able to easily share extensions with users they need to be published somewhere.
+Lens currently only supports installing extensions from NPM tarballs.
+All hosted extensions must, therefore, be retrievable in a NPM tarball.
+
+## Places To Host Your Extension
+
+We recommend to host your extension somewhere on the web so that it is easy for people to search for and download it.
+We recommend either hosting it as an NPM package on https://www.npmjs.com or through GitHub releases.
+We recommend against using GitHub packages (https://github.com/features/packages) as it requires a GitHub token to access the package.
+
+### Publishing via NPM
+
+This is the easiest method of publishing as NPM comes built in with mechanism to get a link to download the package as a tarball.
+Once you have set up an account with NPM (https://www.npmjs.com/signup) and logged in with their CLI (`npm login`) you will be ready to publish.
+
+* Run `npm version ` to bump the version of your extension by the appropriate amount.
+* Run `npm publish` to publish your extension to NPM
+* Run `git push && git push --tags` to push the commit that NPM creates to your git remote.
+
+It is probably a good idea to put into your README.md the following instructions for your users to get the tarball download link.
+
+```bash
+npm view dist.tarball
+```
+
+This will output the link that they will need to give to Lens to install your extension.
+
+### Publish via GitHub Releases
+
+Another method of publishing your extensions is to do so with the releases mechanism built into GitHub.
+We recommend reading [GitHub's Releases Documentation](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository) for how to actually do the steps of a release.
+The following will be a quick walk through on how to make the tarball which will be the released file.
+
+### Making a NPM Tarball of Your Extension
+
+While this is necessary for hosting on GitHub releases, this is also the means for creating a tarball if you plan on hosting on a different file hosting platform.
+
+Say you have your project folder at `~/my-extension/` and you want to create an NPM package we need to do the following within your git repo:
+
+```
+npm pack
+```
+
+This will create a NPM tarball that can be hosted on Github Releases or any other publicly available file hosting service.
diff --git a/docs/extensions/testing-and-publishing/testing.md b/docs/extensions/testing-and-publishing/testing.md
new file mode 100644
index 0000000000..af178efeb7
--- /dev/null
+++ b/docs/extensions/testing-and-publishing/testing.md
@@ -0,0 +1,95 @@
+# Testing Extensions
+
+## Renderer Process Unit Testing
+
+UI components in the extension's renderer process are based on React/ReactDOM.
+These components can be tested by popular React testing tools like [React Testing Library](https://github.com/testing-library/react-testing-library).
+
+If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold extension project then the testing environment for render process is already set up for you.
+Just use `npm start` or `yarn test` to run the tests.
+
+For example, I have a component `GlobalPageMenuIcon` and want to test if `props.navigate` is called when user clicks the icon.
+
+My component `GlobalPageMenuIcon`
+
+```typescript
+import React from "react"
+import { Component: { Icon } } from "@k8slens/extensions";
+
+const GlobalPageMenuIcon = ({ navigate }: { navigate?: () => void }): JSX.Element => (
+ navigate()}
+ data-testid="global-page-menu-icon"
+ />
+)
+```
+
+The test
+
+```js
+import React from "react"
+import { render, screen, fireEvent } from "@testing-library/react";
+
+import GlobalPageMenuIcon from "./GlobalPageMenuIcon";
+
+test("click called navigate()", () => {
+ const navigate = jest.fn();
+ render();
+ fireEvent.click(screen.getByTestId("global-page-menu-icon"));
+ expect(navigate).toHaveBeenCalled();
+ });
+```
+
+In the example we used [React Testing Library](https://github.com/testing-library/react-testing-library) but any React testing framework can be used to test renderer process UI components.
+
+There are more example tests in the generator's [template](https://github.com/lensapp/generator-lens-ext/tree/main/generators/app/templates/ext-ts/components).
+Extend your tests based on the examples.
+
+## Main Process Unit Testing
+
+Code in the extension's main process consists of normal JavaScript files that have access to extension api, you can write unit tests using any testing framework.
+
+If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold your extension project then the [Jest](https://jestjs.io/) testing environment is set up for you.
+Just use `npm start` or `yarn test` to run the tests.
+
+## Tips
+
+### Console.log
+
+Extension developers might find `console.log()` useful for printing out information and errors from extensions.
+To use `console.log()`, note that Lens is based on Electron, and that Electron has two types of processes: [Main and Renderer](https://www.electronjs.org/docs/tutorial/quick-start#main-and-renderer-processes).
+
+### Renderer Process Logs
+
+In the Renderer process, `console.log()` is printed in the Console in Developer Tools (**View** > **Toggle Developer Tools**).
+
+### Main Process Logs
+
+Viewing the logs from the Main process is a little trickier, since they cannot be printed using Developer Tools.
+
+#### macOS
+
+On macOS, view the Main process logs by running Lens from the terminal:
+
+```bash
+/Applications/Lens.app/Contents/MacOS/Lens
+```
+
+You can also use [Console.app](https://support.apple.com/en-gb/guide/console/welcome/mac) to view the Main process logs.
+
+#### Linux
+
+On Linux, you can access the Main process logs using the Lens PID.
+First get the PID:
+
+```bash
+ps aux | grep Lens | grep -v grep
+```
+
+Then get the Main process logs using the PID:
+
+```bash
+tail -f /proc/[pid]/fd/1 # stdout (console.log)
+tail -f /proc/[pid]/fd/2 # stdout (console.error)
+```
diff --git a/docs/extensions/usage/README.md b/docs/extensions/usage/README.md
new file mode 100644
index 0000000000..02b2db223a
--- /dev/null
+++ b/docs/extensions/usage/README.md
@@ -0,0 +1,26 @@
+# Using Extensions
+
+The features that Lens includes out-of-the-box are just the start.
+Lens extensions let you add new features to your installation to support your workflow.
+Rich extensibility model lets extension authors plug directly into the Lens UI and contribute functionality through the same APIs used by Lens itself.
+The start using Lens Extensions go to **File** (or **Lens** on macOS) > **Extensions** in the application menu.
+This is the `Extensions` management page where all the management of the extensions you want to use is done.
+
+
+
+## Installing an Extension
+
+There are three ways to install extensions.
+If you have the extension as a `.tgz` file then dragging and dropping it in the extension management page will install it for you.
+If it is hosted on the web, you can paste the URL and click `Install` and Lens will download and install it.
+The third way is to move the extension into your `~/.k8slens/extensions` (or `C:\Users\\.k8slens\extensions`) folder and Lens will automatically detect it and install the extension.
+
+## Enabling or Disabling an Extension
+
+Go to the extension management page and click either the `Enable` or `Disable` buttons.
+Extensions will be enabled by default when you first install them.
+A disabled extension is not loaded by Lens and is not run.
+
+## Uninstalling an Extension
+
+If, for whatever reason, you wish to remove the installation of an extension simple click the `Uninstall` button. This will remove all the files that Lens would need to run the extension.
diff --git a/docs/extensions/usage/images/extensions.png b/docs/extensions/usage/images/extensions.png
new file mode 100644
index 0000000000..5deb8e4bd2
Binary files /dev/null and b/docs/extensions/usage/images/extensions.png differ