Repository: jupyterlab/extension-examples (language: python)

Issue:
Use the new menu definition from settings
Some examples were also simplified to focus on the advertise feature.

Fixes #168
Fixes #181 


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 7e8498f8..b744ceb6 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ jlpm run build
 jupyter lab
 ```
 
-The examples currently target **JupyterLab 3.x**.
+The examples currently target **JupyterLab 3.1 or later**.
 
 If you would like to use the examples with JupyterLab 2.x, check out the [2.x branch](https://github.com/jupyterlab/extension-examples/tree/2.x).
 
diff --git a/command-palette/package.json b/command-palette/package.json
index 38f76e0b..2dc2bb51 100644
--- a/command-palette/package.json
+++ b/command-palette/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/commands/package.json b/commands/package.json
index a06fc28f..e6b35a3c 100644
--- a/commands/package.json
+++ b/commands/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/completer/README.md b/completer/README.md
index 7e90fae8..05b14a94 100644
--- a/completer/README.md
+++ b/completer/README.md
@@ -124,7 +124,7 @@ fetch(
 [_JupyterLab_'s completer-extension](https://github.com/jupyterlab/jupyterlab/tree/master/packages/completer-extension) includes a notebooks plugin that registers notebooks for code completion. Your extension will override the notebooks plugin's behavior, so you can [disable notebooks](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#disabling-other-extensions) in your `.package.json`:
 
 ```json5
-// package.json#L83-L90
+// package.json#L81-L88
 
 "jupyterlab": {
   "extension": true,
diff --git a/completer/package.json b/completer/package.json
index 2ce82f02..d2a7e090 100644
--- a/completer/package.json
+++ b/completer/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -52,7 +51,6 @@
     "@jupyterlab/codeeditor": "^3.1.0",
     "@jupyterlab/completer": "^3.1.0",
     "@jupyterlab/launcher": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/notebook": "^3.1.0",
     "@jupyterlab/outputarea": "^3.1.0",
     "@jupyterlab/statedb": "^3.1.0",
diff --git a/context-menu/README.md b/context-menu/README.md
index 863360a7..a45beb14 100644
--- a/context-menu/README.md
+++ b/context-menu/README.md
@@ -6,7 +6,7 @@ This is a basic example to show how to add a new entry to an existent context me
 
 ![context menu example](preview.gif)
 
-In JupyterLab plugins can expose context menus to offer an easy way to execute commands and perform actions. In this example, you will learn how to add a new entry to the file browser context menu, and at the same time how to register a new file type.
+In JupyterLab plugins can expose context menus to offer an easy way to execute commands and perform actions. In this example, you will learn how to add a new entry to the file browser context menu to be displayed on files with extension `.example`.
 
 > It is strongly recommended to read [commands](https://github.com/jupyterlab/extension-examples/tree/master/commands) example before diving into this one.
 
@@ -18,50 +18,23 @@ First of all, you will start looking into the declaration of the extension:
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L9-L14
+// src/index.ts#L9-L13
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'context-menu',
   autoStart: true,
   requires: [IFileBrowserFactory],
-  optional: [],
   activate: (app: JupyterFrontEnd, factory: IFileBrowserFactory) => {
 ```
 <!-- prettier-ignore-end -->
 
 For this extension, you need to require `IFileBrowserFactory` to track the file browser item clicked by the user.
 
-The example shows you how to create a new file type and add the entry to the context menu only to this file type. The first step is optional, you can also add your button to an existing file type (or all of them!).
-
-To register a new file type, you need to call the `addFileType()` method of `docRegistry` property present in the `JupyterFrontEnd` object. This method requires an `IFileType` object with some properties to define your file type. The most important are:
-
-- `name`: the new file type.
-- `extension`: the list of extensions.
-- `fileFormat`: the file content format (_base64_, _json_ or _text_).
-- `contentType`: the file type (_directory_, _notebook_ or _file_).
-- `mimeType`: the content mime type.
+The first step is to define the command that will be executed when clicking on the context menu entry. If you want to access the item information, you need to use the `IFileBrowserFactory` object to obtain the file browser selected item.
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L15-L23
-
-app.docRegistry.addFileType({
-  name: 'example',
-  icon: runIcon,
-  displayName: 'Example File',
-  extensions: ['.example'],
-  fileFormat: 'text',
-  contentType: 'file',
-  mimeTypes: ['text/plain'],
-});
-```
-<!-- prettier-ignore-end -->
-
-The next step is to define the command that will be executed when clicking on the context menu entry. If you want to access the item information, you need to use the `IFileBrowserFactory` object to obtain the file browser selected item.
-
-<!-- prettier-ignore-start -->
-```ts
-// src/index.ts#L25-L38
+// src/index.ts#L14-L27
 
 app.commands.addCommand('jlab-examples/context-menu:open', {
   label: 'Example',
@@ -80,24 +53,32 @@ app.commands.addCommand('jlab-examples/context-menu:open', {
 ```
 <!-- prettier-ignore-end -->
 
-Finally, you can add the command to a context menu using the `addItem()` method present in the `contextMenu` property. This method requires an `IItemOptions` object with the following properties:
+Then, you can add the command to a context menu using the settings file.
+You will need to define a context menu item under the property `context` of the special key
+`jupyter.lab.menus`. And the properties of the context menu item are:
 
 - `command`: the command to execute.
-- `selector`: the CSS classes of the element where you want to add the entry.
-- `rank`: the position in the context menu
+- `selector`: the CSS classes of the element on which you want to add the entry.
+- `rank`: (optional) number to order the context menu entries
 
 <!-- prettier-ignore-start -->
-```ts
-// src/index.ts#L40-L44
-
-app.contextMenu.addItem({
-  command: 'jlab-examples/context-menu:open',
-  selector: '.jp-DirListing-item[data-file-type="example"]',
-  rank: 0,
-});
+```json5
+// schema/plugin.json#L4-L12
+
+"jupyter.lab.menus": {
+  "context": [
+    {
+      "command": "jlab-examples/context-menu:open",
+      "selector": ".jp-DirListing-item[data-file-type=\"text\"]",
+      "rank": 0
+    }
+  ]
+},
 ```
 <!-- prettier-ignore-end -->
 
-The `selector` can be any valid [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors). In this case, the first part is the CSS class that identifies the file browser items `.jp-DirListing-item` and the second part `[data-file-type="example"]` is a attribute value to be found on the item. You can omit the second part to add the button to every file type.
+The `selector` can be any valid [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors). In this case, the first part is the CSS class that identifies the file browser items `.jp-DirListing-item` and the second part `[data-file-type="text"]` is a attribute value to be found on the item; the `data-file-type` attribute is set with the file type name or `text` for generic files. You can omit the second part to add the command to every file type.
 
 You can find some of the CSS classes that identify different widgets in JupyterLab in the [developer documentation](https://jupyterlab.readthedocs.io/en/stable/developer/css.html#commonly-used-css-selectors).
+
+> See also the [documentation](https://jupyterlab.readthedocs.io/en/stable/extension/extension_points.html#context-menu).
diff --git a/context-menu/package.json b/context-menu/package.json
index 2f84cc97..0894bbeb 100644
--- a/context-menu/package.json
+++ b/context-menu/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -73,7 +73,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_context_menu/labextension"
+    "outputDir": "jupyterlab_examples_context_menu/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/context-menu/schema/plugin.json b/context-menu/schema/plugin.json
new file mode 100644
index 00000000..e416dbc2
--- /dev/null
+++ b/context-menu/schema/plugin.json
@@ -0,0 +1,15 @@
+{
+  "title": "Context Menu Example",
+  "description": "Context Menu Example settings.",
+  "jupyter.lab.menus": {
+    "context": [
+      {
+        "command": "jlab-examples/context-menu:open",
+        "selector": ".jp-DirListing-item[data-file-type=\"text\"]",
+        "rank": 0
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/context-menu/src/index.ts b/context-menu/src/index.ts
index 8b9da3e9..730ff295 100644
--- a/context-menu/src/index.ts
+++ b/context-menu/src/index.ts
@@ -4,24 +4,13 @@ import {
 } from '@jupyterlab/application';
 import { IFileBrowserFactory } from '@jupyterlab/filebrowser';
 import { showDialog, Dialog } from '@jupyterlab/apputils';
-import { buildIcon, runIcon } from '@jupyterlab/ui-components';
+import { buildIcon } from '@jupyterlab/ui-components';
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'context-menu',
   autoStart: true,
   requires: [IFileBrowserFactory],
-  optional: [],
   activate: (app: JupyterFrontEnd, factory: IFileBrowserFactory) => {
-    app.docRegistry.addFileType({
-      name: 'example',
-      icon: runIcon,
-      displayName: 'Example File',
-      extensions: ['.example'],
-      fileFormat: 'text',
-      contentType: 'file',
-      mimeTypes: ['text/plain'],
-    });
-
     app.commands.addCommand('jlab-examples/context-menu:open', {
       label: 'Example',
       caption: "Example context menu button for file browser's items.",
@@ -36,12 +25,6 @@ const extension: JupyterFrontEndPlugin<void> = {
         }).catch((e) => console.log(e));
       },
     });
-
-    app.contextMenu.addItem({
-      command: 'jlab-examples/context-menu:open',
-      selector: '.jp-DirListing-item[data-file-type="example"]',
-      rank: 0,
-    });
   },
 };
 
diff --git a/custom-log-console/README.md b/custom-log-console/README.md
index 86a2a359..88f0a866 100644
--- a/custom-log-console/README.md
+++ b/custom-log-console/README.md
@@ -25,18 +25,16 @@ First of all, you will start by looking into the declaration of the extension:
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L28-L39
+// src/index.ts#L26-L35
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'custom-log-console',
   autoStart: true,
-  requires: [ICommandPalette, IRenderMimeRegistry, IMainMenu, ILayoutRestorer],
-  optional: [],
+  requires: [ICommandPalette, IRenderMimeRegistry, ILayoutRestorer],
   activate: (
     app: JupyterFrontEnd,
     palette: ICommandPalette,
     rendermime: IRenderMimeRegistry,
-    mainMenu: IMainMenu,
     restorer: ILayoutRestorer
   ) => {
 ```
@@ -48,7 +46,7 @@ In the `activate` function, the first step is to declare `logConsolePanel` and `
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L42-L43
+// src/index.ts#L38-L39
 
 let logConsolePanel: LogConsolePanel = null;
 let logConsoleWidget: MainAreaWidget<LogConsolePanel> = null;
@@ -59,7 +57,7 @@ The next step is to create a new widget when clicking on the button to open the
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L76-L76
+// src/index.ts#L72-L72
 
 const createLogConsoleWidget = (): void => {
 ```
@@ -69,7 +67,7 @@ To initialize a new `LogConsoleWidget` you have to create a `LogConsolePanel` to
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L77-L82
+// src/index.ts#L73-L78
 
 logConsolePanel = new LogConsolePanel(
   new LoggerRegistry({
@@ -84,7 +82,7 @@ The `source` property identifies where the message comes from and is necessary t
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L84-L84
+// src/index.ts#L80-L80
 
 logConsolePanel.source = 'custom-log-console';
 ```
@@ -94,7 +92,7 @@ Now you are ready to initialize a new `MainAreaWidget` passing the `logConsolePa
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L86-L88
+// src/index.ts#L82-L84
 
 logConsoleWidget = new MainAreaWidget<LogConsolePanel>({
   content: logConsolePanel,
@@ -106,7 +104,7 @@ The last step of the function `createLogConsoleWidget` is to establish how to pr
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L112-L116
+// src/index.ts#L108-L112
 
 logConsoleWidget.disposed.connect(() => {
   logConsoleWidget = null;
@@ -121,7 +119,7 @@ To launch a new log console, you can add a new command. In this case, you can us
 <!-- prettier-ignore-start -->
 
 ```ts
-// src/index.ts#L125-L136
+// src/index.ts#L121-L132
 
 commands.addCommand('jlab-examples/custom-log-console:open', {
   label: 'Custom Log Console',
@@ -142,7 +140,7 @@ Finally, you can send log messages calling `log` method present on the `logger`
 - HTML message with `IHtmlLog`:
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L147-L153
+// src/index.ts#L143-L149
 
 const msg: IHtmlLog = {
   type: 'html',
@@ -158,7 +156,7 @@ logConsolePanel?.logger?.log(msg);
   <!-- prettier-ignore-start -->
 
 ```ts
-// src/index.ts#L161-L167
+// src/index.ts#L157-L163
 
 const msg: ITextLog = {
   type: 'text',
@@ -175,7 +173,7 @@ logConsolePanel?.logger?.log(msg);
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L175-L188
+// src/index.ts#L171-L184
 
 const data: nbformat.IOutput = {
   output_type: 'display_data',
diff --git a/custom-log-console/package.json b/custom-log-console/package.json
index 1b8f1e42..5152ca28 100644
--- a/custom-log-console/package.json
+++ b/custom-log-console/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -52,7 +52,6 @@
     "@jupyterlab/apputils": "^3.1.0",
     "@jupyterlab/coreutils": "^5.1.0",
     "@jupyterlab/logconsole": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/nbformat": "^3.1.0",
     "@jupyterlab/rendermime": "^3.1.0",
     "@jupyterlab/ui-components": "^3.1.0",
@@ -79,7 +78,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_custom_log_console/labextension"
+    "outputDir": "jupyterlab_examples_custom_log_console/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/custom-log-console/schema/plugin.json b/custom-log-console/schema/plugin.json
new file mode 100644
index 00000000..edad10e2
--- /dev/null
+++ b/custom-log-console/schema/plugin.json
@@ -0,0 +1,32 @@
+{
+  "title": "Log Console Example",
+  "description": "Log Console Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-log-console",
+        "label": "Log Console Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "jlab-examples/custom-log-console:open"
+          },
+          {
+            "type": "separator"
+          },
+          {
+            "command": "jlab-examples/custom-log-console:logHTMLMessage"
+          },
+          {
+            "command": "jlab-examples/custom-log-console:logTextMessage"
+          },
+          {
+            "command": "jlab-examples/custom-log-console:logOutputMessage"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/custom-log-console/src/index.ts b/custom-log-console/src/index.ts
index d964a816..133b9d56 100644
--- a/custom-log-console/src/index.ts
+++ b/custom-log-console/src/index.ts
@@ -18,8 +18,6 @@ import {
 } from '@jupyterlab/logconsole';
 import { addIcon, clearIcon, listIcon } from '@jupyterlab/ui-components';
 import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
-import { IMainMenu } from '@jupyterlab/mainmenu';
-import { Menu } from '@lumino/widgets';
 
 import * as nbformat from '@jupyterlab/nbformat';
 
@@ -28,13 +26,11 @@ import LogLevelSwitcher from './logLevelSwitcher';
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'custom-log-console',
   autoStart: true,
-  requires: [ICommandPalette, IRenderMimeRegistry, IMainMenu, ILayoutRestorer],
-  optional: [],
+  requires: [ICommandPalette, IRenderMimeRegistry, ILayoutRestorer],
   activate: (
     app: JupyterFrontEnd,
     palette: ICommandPalette,
     rendermime: IRenderMimeRegistry,
-    mainMenu: IMainMenu,
     restorer: ILayoutRestorer
   ) => {
     const { commands } = app;
@@ -188,26 +184,6 @@ const extension: JupyterFrontEndPlugin<void> = {
         logConsolePanel?.logger?.log(msg);
       },
     });
-
-    // Create a new menu
-    const menu: Menu = new Menu({ commands });
-    menu.title.label = 'Log Console Example';
-    mainMenu.addMenu(menu, { rank: 80 });
-
-    // Button to open custom log console
-    menu.addItem({ command: 'jlab-examples/custom-log-console:open' });
-    menu.addItem({ type: 'separator' });
-
-    // Buttons for the different examples
-    menu.addItem({
-      command: 'jlab-examples/custom-log-console:logHTMLMessage',
-    });
-    menu.addItem({
-      command: 'jlab-examples/custom-log-console:logTextMessage',
-    });
-    menu.addItem({
-      command: 'jlab-examples/custom-log-console:logOutputMessage',
-    });
   },
 };
 
diff --git a/datagrid/README.md b/datagrid/README.md
index b9c567d5..6190ab40 100644
--- a/datagrid/README.md
+++ b/datagrid/README.md
@@ -14,11 +14,11 @@ First you need to import `StackedPanel`, `DataGrid`
 and `DataModel` classes from lumino:
 
 ```ts
-// src/index.ts#L16-L18
+// src/index.ts#L14-L16
 
 import { DataGrid, DataModel } from '@lumino/datagrid';
 
-import { Menu, StackedPanel } from '@lumino/widgets';
+import { StackedPanel } from '@lumino/widgets';
 ```
 
 The `StackedPanel` widget can hold several sub-widgets that are added with its
@@ -35,7 +35,7 @@ the main area of JupyterLab as seen in the above screenshot.
 With these three classes, you can create your own widget, called `DataGridPanel` :
 
 ```ts
-// src/index.ts#L57-L77
+// src/index.ts#L48-L68
 
 class DataGridPanel extends StackedPanel {
   constructor(translator?: ITranslator) {
@@ -71,7 +71,7 @@ Therefore in this example a class `LargeDataModel` is derived from it
 to implement its abstract methods:
 
 ```ts
-// src/index.ts#L79-L88
+// src/index.ts#L70-L79
 
 class LargeDataModel extends DataModel {
   rowCount(region: DataModel.RowRegion): number {
@@ -122,7 +122,7 @@ values of the datagrid. In this case it simply displays the row and
 column index in each cell, and adds a letter prefix in the header regions:
 
 ```ts
-// src/index.ts#L88-L99
+// src/index.ts#L79-L90
 
 data(region: DataModel.CellRegion, row: number, column: number): any {
   if (region === 'row-header') {
diff --git a/datagrid/package.json b/datagrid/package.json
index f78488c1..44354d3b 100644
--- a/datagrid/package.json
+++ b/datagrid/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -49,7 +49,6 @@
   },
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/translation": "^3.1.0",
     "@lumino/algorithm": "^1.3.3",
     "@lumino/coreutils": "^1.5.3",
@@ -76,7 +75,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_datagrid/labextension"
+    "outputDir": "jupyterlab_examples_datagrid/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/datagrid/schema/plugin.json b/datagrid/schema/plugin.json
new file mode 100644
index 00000000..94792701
--- /dev/null
+++ b/datagrid/schema/plugin.json
@@ -0,0 +1,20 @@
+{
+  "title": "DataGrid Example",
+  "description": "DataGrid Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-datagrid",
+        "label": "DataGrid Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "examples:datagrid"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/datagrid/src/index.ts b/datagrid/src/index.ts
index 4f2e0234..4f705d09 100644
--- a/datagrid/src/index.ts
+++ b/datagrid/src/index.ts
@@ -5,8 +5,6 @@ import {
 
 import { ICommandPalette } from '@jupyterlab/apputils';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
 import {
   ITranslator,
   nullTranslator,
@@ -15,7 +13,7 @@ import {
 
 import { DataGrid, DataModel } from '@lumino/datagrid';
 
-import { Menu, StackedPanel } from '@lumino/widgets';
+import { StackedPanel } from '@lumino/widgets';
 
 /**
  * Initialization data for the extension1 extension.
@@ -23,11 +21,10 @@ import { Menu, StackedPanel } from '@lumino/widgets';
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'datagrid',
   autoStart: true,
-  requires: [ICommandPalette, IMainMenu, ITranslator],
+  requires: [ICommandPalette, ITranslator],
   activate: (
     app: JupyterFrontEnd,
     palette: ICommandPalette,
-    mainMenu: IMainMenu,
     translator: ITranslator
   ) => {
     const { commands, shell } = app;
@@ -43,12 +40,6 @@ const extension: JupyterFrontEndPlugin<void> = {
       },
     });
     palette.addItem({ command, category: 'Extension Examples' });
-
-    const exampleMenu = new Menu({ commands });
-
-    exampleMenu.title.label = trans.__('DataGrid Example');
-    mainMenu.addMenu(exampleMenu, { rank: 80 });
-    exampleMenu.addItem({ command });
   },
 };
 
diff --git a/documents/README.md b/documents/README.md
index f50fcf65..8ddf9b1d 100644
--- a/documents/README.md
+++ b/documents/README.md
@@ -56,7 +56,7 @@ At the same time, you need to implement the method `createNew`. The `DocumentMan
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/factory.ts#L100-L102
+// src/factory.ts#L109-L111
 
 createNew(languagePreference?: string, modelDB?: IModelDB): ExampleDocModel {
   return new ExampleDocModel(languagePreference, modelDB);
@@ -157,7 +157,7 @@ To create a new shared object, you have to use the `ydoc`. The new attribute wil
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/model.ts#L327-L328
+// src/model.ts#L340-L341
 
 this._content = this.ydoc.getMap('content');
 this._content.observe(this._contentObserver);
@@ -168,7 +168,7 @@ To access the information about the different users connected, you can use the `
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/model.ts#L261-L261
+// src/model.ts#L279-L279
 
 this.sharedModel.awareness.setLocalStateField('mouse', pos);
 ```
@@ -176,7 +176,7 @@ this.sharedModel.awareness.setLocalStateField('mouse', pos);
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/model.ts#L289-L289
+// src/model.ts#L302-L302
 
 const clients = this.sharedModel.awareness.getStates();
 ```
@@ -194,7 +194,7 @@ Every time you modify a shared property, this property triggers an event in all
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/model.ts#L167-L170
+// src/model.ts#L183-L186
 
 this.sharedModel.transact(() => {
   this.sharedModel.setContent('position', { x: obj.x, y: obj.y });
diff --git a/documents/package.json b/documents/package.json
index 259c7647..b4b3af40 100644
--- a/documents/package.json
+++ b/documents/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -86,4 +85,4 @@
     "outputDir": "jupyterlab_examples_documents/labextension"
   },
   "styleModule": "style/index.js"
-}
\ No newline at end of file
+}
diff --git a/documents/src/factory.ts b/documents/src/factory.ts
index edb1ee64..2905cd4c 100644
--- a/documents/src/factory.ts
+++ b/documents/src/factory.ts
@@ -18,7 +18,7 @@ export class ExampleWidgetFactory extends ABCWidgetFactory<
   /**
    * Constructor of ExampleWidgetFactory.
    *
-   * @param options
+   * @param options Constructor options
    */
   constructor(options: DocumentRegistry.IWidgetFactoryOptions) {
     super(options);
@@ -27,8 +27,8 @@ export class ExampleWidgetFactory extends ABCWidgetFactory<
   /**
    * Create a new widget given a context.
    *
-   * @param context: Contains the information of the file
-   * @param context
+   * @param context Contains the information of the file
+   * @returns The widget
    */
   protected createNewWidget(
     context: DocumentRegistry.IContext<ExampleDocModel>
@@ -48,6 +48,8 @@ export class ExampleDocModelFactory
 {
   /**
    * The name of the model.
+   *
+   * @returns The name
    */
   get name(): string {
     return 'example-model';
@@ -55,6 +57,8 @@ export class ExampleDocModelFactory
 
   /**
    * The content type of the file.
+   *
+   * @returns The content type
    */
   get contentType(): Contents.ContentType {
     return 'file';
@@ -62,6 +66,8 @@ export class ExampleDocModelFactory
 
   /**
    * The format of the file.
+   *
+   * @returns the file format
    */
   get fileFormat(): Contents.FileFormat {
     return 'text';
@@ -69,6 +75,8 @@ export class ExampleDocModelFactory
 
   /**
    * Get whether the model factory has been disposed.
+   *
+   * @returns disposed status
    */
   get isDisposed(): boolean {
     return this._disposed;
@@ -84,8 +92,8 @@ export class ExampleDocModelFactory
   /**
    * Get the preferred language given the path on the file.
    *
-   * @param path: path of the file represented by this document model
-   * @param path
+   * @param path path of the file represented by this document model
+   * @returns The preferred language
    */
   preferredLanguage(path: string): string {
     return '';
@@ -94,8 +102,9 @@ export class ExampleDocModelFactory
   /**
    * Create a new instance of ExampleDocModel.
    *
-   * @param languagePreference
-   * @param modelDB
+   * @param languagePreference Language
+   * @param modelDB Model database
+   * @returns The model
    */
   createNew(languagePreference?: string, modelDB?: IModelDB): ExampleDocModel {
     return new ExampleDocModel(languagePreference, modelDB);
diff --git a/documents/src/model.ts b/documents/src/model.ts
index b542e7ad..9d877d4d 100644
--- a/documents/src/model.ts
+++ b/documents/src/model.ts
@@ -30,8 +30,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * Construct a new ExampleDocModel.
    *
-   * @param languagePreference
-   * @param modelDB
+   * @param languagePreference Language
+   * @param modelDB Document model database
    */
   constructor(languagePreference?: string, modelDB?: IModelDB) {
     this.modelDB = modelDB || new ModelDB();
@@ -44,6 +44,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * get/set the dirty attribute to know when the
    * content in the document differs from disk
+   *
+   * @returns dirty attribute
    */
   get dirty(): boolean {
     return this._dirty;
@@ -55,6 +57,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * get/set the readOnly attribute to know whether this model
    * is read only or not
+   *
+   * @returns readOnly attribute
    */
   get readOnly(): boolean {
     return this._readOnly;
@@ -66,6 +70,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * get the isDisposed attribute to know whether this model
    * has been disposed or not
+   *
+   * @returns Model status
    */
   get isDisposed(): boolean {
     return this._isDisposed;
@@ -77,6 +83,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    *
    * NOTE: The content refers to de data stored in the model while the state refers
    * to the metadata or attributes of the model.
+   *
+   * @returns The signal
    */
   get contentChanged(): ISignal<this, void> {
     return this._contentChanged;
@@ -88,6 +96,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    *
    * NOTE: The content refers to de data stored in the model while the state refers
    * to the metadata or attributes of the model.
+   *
+   * @returns The signal
    */
   get stateChanged(): ISignal<this, IChangedArgs<any, any, string>> {
     return this._stateChanged;
@@ -96,6 +106,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * get the signal sharedModelChanged to listen for changes on the content
    * of the shared model.
+   *
+   * @returns The signal
    */
   get sharedModelChanged(): ISignal<this, ExampleDocChange> {
     return this._sharedModelChanged;
@@ -104,6 +116,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * get the signal clientChanged to listen for changes on the clients sharing
    * the same document.
+   *
+   * @returns The signal
    */
   get clientChanged(): ISignal<this, Map<number, any>> {
     return this._clientChanged;
@@ -144,6 +158,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    * Should return the data that you need to store in disk as a string.
    * The context will call this method to get the file's content and save it
    * to disk
+   *
+   * @returns The data
    */
   toString(): string {
     const pos = this.sharedModel.getContent('position');
@@ -160,7 +176,7 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    * This method should implement the logic to parse the data and store it
    * on the datastore.
    *
-   * @param data
+   * @param data Serialized data
    */
   fromString(data: string): void {
     const obj = JSON.parse(data);
@@ -177,6 +193,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    *
    * NOTE: This method is only used by the context of the notebook, every other
    * document will load/save the data through toString/fromString.
+   *
+   * @returns Model JSON representation
    */
   toJSON(): PartialJSONObject {
     const pos = this.sharedModel.getContent('position');
@@ -196,7 +214,7 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    * NOTE: This method is only used by the context of the notebook, every other
    * document will load/save the data through toString/fromString.
    *
-   * @param data
+   * @param data Serialized model
    */
   fromJSON(data: PartialJSONObject): void {
     this.sharedModel.transact(() => {
@@ -236,7 +254,7 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * Sets the position of the SharedObject
    *
-   * @param pos
+   * @param pos Position
    */
   setPosition(pos: Position): void {
     this.sharedModel.setContent('position', pos);
@@ -245,7 +263,7 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * Sets the text inside the SharedObject
    *
-   * @param content
+   * @param content Text
    */
   setContent(content: string): void {
     this.sharedModel.setContent('content', content);
@@ -254,7 +272,7 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
   /**
    * Sets the mouse's position of the client
    *
-   * @param pos
+   * @param pos Mouse position
    */
   setClient(pos: Position): void {
     // Adds the position of the mouse from the client to the shared state.
@@ -265,10 +283,8 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    * Callback to listen for changes on the sharedModel. This callback listens
    * to changes on shared model's content and propagates them to the DocumentWidget.
    *
-   * @param sender: The sharedModel that triggers the changes.
-   * @param change: The changes on the sharedModel.
-   * @param sender
-   * @param changes
+   * @param sender The sharedModel that triggers the changes.
+   * @param changes The changes on the sharedModel.
    */
   private _onSharedModelChanged = (
     sender: ExampleDoc,
@@ -281,9 +297,6 @@ export class ExampleDocModel implements DocumentRegistry.IModel {
    * Callback to listen for changes on the sharedModel. This callback listens
    * to changes on the different clients sharing the document and propagates
    * them to the DocumentWidget.
-   *
-   * @param sender: The sharedModel that triggers the changes.
-   * @param clients: The list of client's states.
    */
   private _onClientChanged = () => {
     const clients = this.sharedModel.awareness.getStates();
@@ -337,6 +350,8 @@ export class ExampleDoc extends YDocument<ExampleDocChange> {
 
   /**
    * Static method to create instances on the sharedModel
+   *
+   * @returns The sharedModel instance
    */
   public static create(): ExampleDoc {
     return new ExampleDoc();
@@ -345,8 +360,8 @@ export class ExampleDoc extends YDocument<ExampleDocChange> {
   /**
    * Returns an the requested object.
    *
-   * @param key: The key of the object.
-   * @param key
+   * @param key The key of the object.
+   * @returns The content
    */
   public getContent(key: string): any {
     return this._content.get(key);
@@ -355,10 +370,8 @@ export class ExampleDoc extends YDocument<ExampleDocChange> {
   /**
    * Adds new data.
    *
-   * @param key: The key of the object.
-   * @param value: New object.
-   * @param key
-   * @param value
+   * @param key The key of the object.
+   * @param value New object.
    */
   public setContent(key: string, value: any): void {
     this._content.set(key, value);
@@ -367,7 +380,7 @@ export class ExampleDoc extends YDocument<ExampleDocChange> {
   /**
    * Handle a change.
    *
-   * @param event
+   * @param event Model event
    */
   private _contentObserver = (event: Y.YMapEvent<any>): void => {
     const changes: ExampleDocChange = {};
diff --git a/documents/src/widget.tsx b/documents/src/widget.tsx
index 2dcd83cf..8d9433b3 100644
--- a/documents/src/widget.tsx
+++ b/documents/src/widget.tsx
@@ -96,7 +96,7 @@ export class ExamplePanel extends Widget {
   /**
    * Handle `after-attach` messages sent to the widget.
    *
-   * @param msg
+   * @param msg Widget layout message
    */
   protected onAfterAttach(msg: Message): void {
     super.onAfterAttach(msg);
@@ -110,7 +110,7 @@ export class ExamplePanel extends Widget {
   /**
    * Handle `before-detach` messages sent to the widget.
    *
-   * @param msg
+   * @param msg Widget layout message
    */
   protected onBeforeDetach(msg: Message): void {
     super.onBeforeDetach(msg);
@@ -124,7 +124,7 @@ export class ExamplePanel extends Widget {
   /**
    * Handle event messages sent to the widget.
    *
-   * @param event
+   * @param event Event on the widget
    */
   public handleEvent(event: MouseEvent): void {
     event.preventDefault();
@@ -176,10 +176,8 @@ export class ExamplePanel extends Widget {
    * Callback to listen for changes on the model. This callback listens
    * to changes on shared model's content.
    *
-   * @param sender: The DocumentModel that triggers the changes.
-   * @param change: The changes on the model
-   * @param sender
-   * @param change
+   * @param sender The DocumentModel that triggers the changes.
+   * @param change The changes on the model
    */
   private _onContentChanged = (
     sender: ExampleDocModel,
@@ -198,10 +196,8 @@ export class ExamplePanel extends Widget {
    * Callback to listen for changes on the model. This callback listens
    * to changes on the different clients sharing the document.
    *
-   * @param sender: The DocumentModel that triggers the changes.
-   * @param clients: The list of client's states.
-   * @param sender
-   * @param clients
+   * @param sender The DocumentModel that triggers the changes.
+   * @param clients The list of client's states.
    */
   private _onClientChanged = (
     sender: ExampleDocModel,
diff --git a/hello-world/README.md b/hello-world/README.md
index ce8cb818..a8a0ea80 100644
--- a/hello-world/README.md
+++ b/hello-world/README.md
@@ -120,7 +120,7 @@ called `@jupyterlab/application`. The dependency of your extension on this
 package is declared in the file `package.json`:
 
 ```json5
-// package.json#L50-L52
+// package.json#L49-L51
 
 "dependencies": {
   "@jupyterlab/application": "^3.1.0"
diff --git a/hello-world/package.json b/hello-world/package.json
index ea48e9bd..9353d07d 100644
--- a/hello-world/package.json
+++ b/hello-world/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/kernel-messaging/README.md b/kernel-messaging/README.md
index 7589b590..45024760 100644
--- a/kernel-messaging/README.md
+++ b/kernel-messaging/README.md
@@ -68,7 +68,7 @@ The session manager object is
 provided directly by the JupyterLab application:
 
 ```ts
-// src/index.ts#L52-L52
+// src/index.ts#L46-L46
 
 const manager = app.serviceManager;
 ```
diff --git a/kernel-messaging/package.json b/kernel-messaging/package.json
index eec28dc6..0ee4ec3d 100644
--- a/kernel-messaging/package.json
+++ b/kernel-messaging/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -50,7 +50,6 @@
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
     "@jupyterlab/launcher": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/nbformat": "^3.1.0",
     "@jupyterlab/translation": "^3.1.0",
     "@lumino/algorithm": "^1.3.3",
@@ -79,7 +78,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_kernel_messaging/labextension"
+    "outputDir": "jupyterlab_examples_kernel_messaging/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/kernel-messaging/schema/plugin.json b/kernel-messaging/schema/plugin.json
new file mode 100644
index 00000000..91b857ac
--- /dev/null
+++ b/kernel-messaging/schema/plugin.json
@@ -0,0 +1,20 @@
+{
+  "title": "Kernel Messaging Example",
+  "description": "Kernel Messaging Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-kernel-messaging",
+        "label": "Kernel Messaging",
+        "rank": 80,
+        "items": [
+          {
+            "command": "kernel-messaging:create"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/kernel-messaging/src/index.ts b/kernel-messaging/src/index.ts
index dc205393..08f6da2a 100644
--- a/kernel-messaging/src/index.ts
+++ b/kernel-messaging/src/index.ts
@@ -7,12 +7,8 @@ import { ICommandPalette } from '@jupyterlab/apputils';
 
 import { ILauncher } from '@jupyterlab/launcher';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
 import { ITranslator } from '@jupyterlab/translation';
 
-import { Menu } from '@lumino/widgets';
-
 import { ExamplePanel } from './panel';
 
 /**
@@ -29,7 +25,7 @@ const extension: JupyterFrontEndPlugin<void> = {
   id: 'kernel-messaging',
   autoStart: true,
   optional: [ILauncher],
-  requires: [ICommandPalette, IMainMenu, ITranslator],
+  requires: [ICommandPalette, ITranslator],
   activate: activate,
 };
 
@@ -38,14 +34,12 @@ const extension: JupyterFrontEndPlugin<void> = {
  *
  * @param app Jupyter Front End
  * @param palette Jupyter Commands Palette
- * @param mainMenu Jupyter Menu
  * @param translator Jupyter Translator
  * @param launcher [optional] Jupyter Launcher
  */
 function activate(
   app: JupyterFrontEnd,
   palette: ICommandPalette,
-  mainMenu: IMainMenu,
   translator: ITranslator,
   launcher: ILauncher | null
 ): void {
@@ -73,11 +67,6 @@ function activate(
     return panel;
   }
 
-  // add menu tab
-  const exampleMenu = new Menu({ commands });
-  exampleMenu.title.label = trans.__('Kernel Messaging');
-  mainMenu.addMenu(exampleMenu);
-
   // add commands to registry
   commands.addCommand(CommandIDs.create, {
     label: trans.__('Open the Kernel Messaging Panel'),
@@ -87,7 +76,6 @@ function activate(
 
   // add items in command palette and menu
   palette.addItem({ command: CommandIDs.create, category });
-  exampleMenu.addItem({ command: CommandIDs.create });
 }
 
 export default extension;
diff --git a/kernel-output/README.md b/kernel-output/README.md
index 85288482..88c634c2 100644
--- a/kernel-output/README.md
+++ b/kernel-output/README.md
@@ -1,6 +1,6 @@
 # Kernel Output
 
-> Render kernel messages in an OuputArea.
+> Render kernel messages in an _OuputArea_.
 
 - [Code structure](#code-structure)
 - [Initializing a Kernel Session](#initializing-a-kernel-session)
@@ -25,7 +25,7 @@ example.
 The code is split into two parts:
 
 1.  the JupyterLab plugin that activates all the extension components and connects
-    them to the main _JupyterLab_ application via commands, launcher and menu
+    them to the main _JupyterLab_ application via commands and launcher
     items,
 2.  a panel that contains the extension logic and UI elements to interact with it.
 
@@ -172,7 +172,7 @@ The last step is to add the panel to the JupyterLab main area.
 First, it is a good practice to unify the extension commands into one namespace at the top of the file:
 
 ```ts
-// src/index.ts#L23-L27
+// src/index.ts#L19-L23
 
 namespace CommandIDs {
   export const create = 'kernel-output:create';
@@ -181,16 +181,15 @@ namespace CommandIDs {
 }
 ```
 
-You can then add the commands to the palette and the menu by iterating
+You can then add the commands to the palette by iterating
 on a list:
 
 ```ts
-// src/index.ts#L110-L114
+// src/index.ts#L99-L102
 
 // add items in command palette and menu
 [CommandIDs.create, CommandIDs.execute].forEach((command) => {
   palette.addItem({ command, category });
-  exampleMenu.addItem({ command });
 });
 ```
 
@@ -198,7 +197,7 @@ To create a new client session, the service manager must be obtained from
 the JupyterLab application:
 
 ```ts
-// src/index.ts#L58-L58
+// src/index.ts#L52-L52
 
 const manager = app.serviceManager;
 ```
@@ -208,7 +207,7 @@ ready. Then once the panel is created and its session is ready, it
 can be added to the JupyterLab main area:
 
 ```ts
-// src/index.ts#L63-L74
+// src/index.ts#L57-L68
 
 let panel: ExamplePanel;
 
@@ -231,7 +230,7 @@ to be executed by the kernel. Then you will send it to your panel for execution
 and display:
 
 ```ts
-// src/index.ts#L88-L108
+// src/index.ts#L77-L97
 
 commands.addCommand(CommandIDs.execute, {
   label: trans.__('Contact Kernel and Execute Code'),
diff --git a/kernel-output/package.json b/kernel-output/package.json
index ecf85ee3..7e50963c 100644
--- a/kernel-output/package.json
+++ b/kernel-output/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -50,7 +50,6 @@
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
     "@jupyterlab/launcher": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/outputarea": "^3.1.0",
     "@jupyterlab/translation": "^3.1.0",
     "@lumino/algorithm": "^1.3.3",
@@ -78,7 +77,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_kernel_output/labextension"
+    "outputDir": "jupyterlab_examples_kernel_output/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/kernel-output/schema/plugin.json b/kernel-output/schema/plugin.json
new file mode 100644
index 00000000..108a464f
--- /dev/null
+++ b/kernel-output/schema/plugin.json
@@ -0,0 +1,23 @@
+{
+  "title": "Kernel Output Example",
+  "description": "Kernel Output Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-kernel-output",
+        "label": "Kernel Output",
+        "rank": 80,
+        "items": [
+          {
+            "command": "kernel-output:create"
+          },
+          {
+            "command": "kernel-output:execute"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/kernel-output/src/index.ts b/kernel-output/src/index.ts
index 45b25bec..da5714d7 100644
--- a/kernel-output/src/index.ts
+++ b/kernel-output/src/index.ts
@@ -7,14 +7,10 @@ import { ICommandPalette, InputDialog } from '@jupyterlab/apputils';
 
 import { ILauncher } from '@jupyterlab/launcher';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
 import { ITranslator } from '@jupyterlab/translation';
 
 import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
 
-import { Menu } from '@lumino/widgets';
-
 import { ExamplePanel } from './panel';
 
 /**
@@ -33,7 +29,7 @@ const extension: JupyterFrontEndPlugin<void> = {
   id: 'kernel-output',
   autoStart: true,
   optional: [ILauncher],
-  requires: [ICommandPalette, IMainMenu, IRenderMimeRegistry, ITranslator],
+  requires: [ICommandPalette, IRenderMimeRegistry, ITranslator],
   activate: activate,
 };
 
@@ -42,7 +38,6 @@ const extension: JupyterFrontEndPlugin<void> = {
  *
  * @param app Jupyter Front End
  * @param palette Jupyter Commands Palette
- * @param mainMenu Jupyter Menu
  * @param rendermime Jupyter Render Mime Registry
  * @param translator Jupyter Translator
  * @param launcher [optional] Jupyter Launcher
@@ -50,7 +45,6 @@ const extension: JupyterFrontEndPlugin<void> = {
 function activate(
   app: JupyterFrontEnd,
   palette: ICommandPalette,
-  mainMenu: IMainMenu,
   rendermime: IRenderMimeRegistry,
   translator: ITranslator,
   launcher: ILauncher | null
@@ -73,11 +67,6 @@ function activate(
     return panel;
   }
 
-  // add menu tab
-  const exampleMenu = new Menu({ commands });
-  exampleMenu.title.label = trans.__('Kernel Output');
-  mainMenu.addMenu(exampleMenu);
-
   // add commands to registry
   commands.addCommand(CommandIDs.create, {
     label: trans.__('Open the Kernel Output Panel'),
@@ -110,7 +99,6 @@ function activate(
   // add items in command palette and menu
   [CommandIDs.create, CommandIDs.execute].forEach((command) => {
     palette.addItem({ command, category });
-    exampleMenu.addItem({ command });
   });
 
   // Add launcher
diff --git a/launcher/README.md b/launcher/README.md
index e64ec417..1b2f3066 100644
--- a/launcher/README.md
+++ b/launcher/README.md
@@ -10,15 +10,16 @@ dependencies to JupyterLab features.
 > Credit: This example is copied from Jeremy Tuloup [Python file extension](https://github.com/jtpio/jupyterlab-python-file).
 
 This example allows to create an empty Python file. To do so,
-your extension will use two commands defined by the [documents manager](https://github.com/jupyterlab/jupyterlab/blob/master/packages/docmanager-extension/src/index.ts#L53-L86) of JupyterLab:
+your extension will use two commands defined by the [documents manager](https://github.com/jupyterlab/jupyterlab/blob/master/packages/docmanager-extension/src/index.tsx#L52-L82) of JupyterLab:
 
 - `'docmanager:new-untitled'`: Create new untitled document
 - `'docmanager:open'`: Open a document
 
 The command will create a new Python file and then open it:
 
+<!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L44-L66
+// src/index.ts#L41-L64
 
 commands.addCommand(command, {
   label: (args) => (args['isPalette'] ? 'New Python File' : 'Python File'),
@@ -27,7 +28,8 @@ commands.addCommand(command, {
   execute: async (args) => {
     // Get the directory in which the Python file must be created;
     // otherwise take the current filebrowser directory
-    const cwd = args['cwd'] || browserFactory.defaultBrowser.model.path;
+    const cwd =
+      args['cwd'] || browserFactory.tracker.currentWidget.model.path;
 
     // Create a new untitled python file
     const model = await commands.execute('docmanager:new-untitled', {
@@ -44,6 +46,7 @@ commands.addCommand(command, {
   },
 });
 ```
+<!-- prettier-ignore-end -->
 
 To link that command to the JupyterLab launcher, the `ILauncher` interface needs to be passed to the `activate`
 extension function. As that interface is provided by the `@jupyterlab/launcher` package, it needs first to be installed:
@@ -63,18 +66,17 @@ import { ILauncher } from '@jupyterlab/launcher';
 And finally you can add it to the list of dependencies:
 
 ```ts
-// src/index.ts#L25-L36
+// src/index.ts#L23-L33
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'launcher',
   autoStart: true,
   requires: [IFileBrowserFactory],
-  optional: [ILauncher, IMainMenu, ICommandPalette],
+  optional: [ILauncher, ICommandPalette],
   activate: (
     app: JupyterFrontEnd,
     browserFactory: IFileBrowserFactory,
     launcher: ILauncher | null,
-    menu: IMainMenu | null,
     palette: ICommandPalette | null
   ) => {
 ```
@@ -90,7 +92,7 @@ Therefore before adding the command to the launcher, you need to check if the `l
 variable is not `null`:
 
 ```ts
-// src/index.ts#L68-L75
+// src/index.ts#L66-L73
 
 // Add the command to the launcher
 if (launcher) {
diff --git a/launcher/package.json b/launcher/package.json
index 3ed6fef2..3a0f42de 100644
--- a/launcher/package.json
+++ b/launcher/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -50,8 +50,7 @@
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
     "@jupyterlab/filebrowser": "^3.1.0",
-    "@jupyterlab/launcher": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0"
+    "@jupyterlab/launcher": "^3.1.0"
   },
   "devDependencies": {
     "@jupyterlab/builder": "^3.1.0",
@@ -74,7 +73,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_launcher/labextension"
+    "outputDir": "jupyterlab_examples_launcher/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/launcher/schema/plugin.json b/launcher/schema/plugin.json
new file mode 100644
index 00000000..dba59271
--- /dev/null
+++ b/launcher/schema/plugin.json
@@ -0,0 +1,25 @@
+{
+  "title": "Launcher Example",
+  "description": "Launcher Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-file",
+        "items": [
+          {
+            "type": "submenu",
+            "id": "jp-mainmenu-file-new",
+            "items": [
+              {
+                "command": "jlab-examples:create-new-python-file",
+                "rank": 30
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/launcher/src/index.ts b/launcher/src/index.ts
index c7ad3b03..0b931c22 100644
--- a/launcher/src/index.ts
+++ b/launcher/src/index.ts
@@ -9,8 +9,6 @@ import { IFileBrowserFactory } from '@jupyterlab/filebrowser';
 
 import { ILauncher } from '@jupyterlab/launcher';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
 import { LabIcon } from '@jupyterlab/ui-components';
 
 import pythonIconStr from '../style/Python-logo-notext.svg';
@@ -26,12 +24,11 @@ const extension: JupyterFrontEndPlugin<void> = {
   id: 'launcher',
   autoStart: true,
   requires: [IFileBrowserFactory],
-  optional: [ILauncher, IMainMenu, ICommandPalette],
+  optional: [ILauncher, ICommandPalette],
   activate: (
     app: JupyterFrontEnd,
     browserFactory: IFileBrowserFactory,
     launcher: ILauncher | null,
-    menu: IMainMenu | null,
     palette: ICommandPalette | null
   ) => {
     const { commands } = app;
@@ -48,7 +45,8 @@ const extension: JupyterFrontEndPlugin<void> = {
       execute: async (args) => {
         // Get the directory in which the Python file must be created;
         // otherwise take the current filebrowser directory
-        const cwd = args['cwd'] || browserFactory.defaultBrowser.model.path;
+        const cwd =
+          args['cwd'] || browserFactory.tracker.currentWidget.model.path;
 
         // Create a new untitled python file
         const model = await commands.execute('docmanager:new-untitled', {
@@ -82,11 +80,6 @@ const extension: JupyterFrontEndPlugin<void> = {
         category: PALETTE_CATEGORY,
       });
     }
-
-    // Add the command to the menu
-    if (menu) {
-      menu.fileMenu.newMenu.addGroup([{ command }], 30);
-    }
   },
 };
 
diff --git a/log-messages/README.md b/log-messages/README.md
index c97c17ad..0234383c 100644
--- a/log-messages/README.md
+++ b/log-messages/README.md
@@ -23,16 +23,14 @@ First of all, you will start looking into the declaration of the extension:
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L10-L20
+// src/index.ts#L8-L16
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'log-messages',
   autoStart: true,
-  requires: [IMainMenu, ILoggerRegistry, INotebookTracker],
-  optional: [],
+  requires: [ILoggerRegistry, INotebookTracker],
   activate: (
     app: JupyterFrontEnd,
-    mainMenu: IMainMenu,
     loggerRegistry: ILoggerRegistry,
     nbtracker: INotebookTracker
   ) => {
@@ -45,7 +43,7 @@ The first step is to obtain the logger of the active notebook. You can use `logg
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L27-L29
+// src/index.ts#L23-L25
 
 const logger = loggerRegistry.getLogger(
   nbtracker.currentWidget?.context.path
@@ -57,7 +55,7 @@ Finally, you can send log messages by calling the `log` method of the `logger` o
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L32-L38
+// src/index.ts#L28-L34
 
 const msg: ITextLog = {
   type: 'text',
diff --git a/log-messages/package.json b/log-messages/package.json
index 66d7d853..3c14b395 100644
--- a/log-messages/package.json
+++ b/log-messages/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -52,7 +52,6 @@
     "@jupyterlab/apputils": "^3.1.0",
     "@jupyterlab/coreutils": "^5.1.0",
     "@jupyterlab/logconsole": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/nbformat": "^3.1.0",
     "@jupyterlab/notebook": "^3.1.0",
     "@jupyterlab/rendermime": "^3.1.0",
@@ -80,7 +79,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_log_messages/labextension"
+    "outputDir": "jupyterlab_examples_log_messages/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/log-messages/schema/plugin.json b/log-messages/schema/plugin.json
new file mode 100644
index 00000000..5552d376
--- /dev/null
+++ b/log-messages/schema/plugin.json
@@ -0,0 +1,20 @@
+{
+  "title": "Log Messages Example",
+  "description": "Log Messages Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-log-messages",
+        "label": "Log Messages Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "jlab-examples/log-messages:logTextMessage"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/log-messages/src/index.ts b/log-messages/src/index.ts
index a54add54..3163cc65 100644
--- a/log-messages/src/index.ts
+++ b/log-messages/src/index.ts
@@ -4,17 +4,13 @@ import {
 } from '@jupyterlab/application';
 import { ILoggerRegistry, ITextLog } from '@jupyterlab/logconsole';
 import { INotebookTracker } from '@jupyterlab/notebook';
-import { IMainMenu } from '@jupyterlab/mainmenu';
-import { Menu } from '@lumino/widgets';
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'log-messages',
   autoStart: true,
-  requires: [IMainMenu, ILoggerRegistry, INotebookTracker],
-  optional: [],
+  requires: [ILoggerRegistry, INotebookTracker],
   activate: (
     app: JupyterFrontEnd,
-    mainMenu: IMainMenu,
     loggerRegistry: ILoggerRegistry,
     nbtracker: INotebookTracker
   ) => {
@@ -38,14 +34,6 @@ const extension: JupyterFrontEndPlugin<void> = {
         logger?.log(msg);
       },
     });
-
-    // Create a new menu
-    const menu: Menu = new Menu({ commands });
-    menu.title.label = 'Log Messages Example';
-    console.log(mainMenu);
-    mainMenu.addMenu(menu, { rank: 80 });
-
-    menu.addItem({ command: 'jlab-examples/log-messages:logTextMessage' });
   },
 };
 
diff --git a/main-menu/README.md b/main-menu/README.md
index 58a7d21c..74ae832a 100644
--- a/main-menu/README.md
+++ b/main-menu/README.md
@@ -9,94 +9,107 @@ the notion of _Commands_ as explained in the [commands example](../commands/READ
 One of the possibilities offered to the user to trigger that command is to call it from
 a menu item.
 
-Adding new menu item works in a similar way to the [command palette](../command-palette/README.md).
-The `IMainMenu` interface can be requested as a new argument to the `activate`
-function, but first it has to be imported. And the class `Menu` to create new
-menu needs also to be imported but from the Lumino library:
+Since JupyterLab 3.1, the preferred way to define menus is through a description
+stored in plugin settings file. On the Typescript code, you only need
+to define the _commands_ to be displayed in the menu(s). The code to add
+a command will first be described.
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L6-L8
-
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
-import { Menu } from '@lumino/widgets';
+// src/index.ts#L16-L31
+
+const { commands } = app;
+
+// Add a command
+const command = 'jlab-examples:main-menu';
+commands.addCommand(command, {
+  label: 'Execute jlab-examples:main-menu Command',
+  caption: 'Execute jlab-examples:main-menu Command',
+  execute: (args: any) => {
+    console.log(
+      `jlab-examples:main-menu has been called ${args['origin']}.`
+    );
+    window.alert(
+      `jlab-examples:main-menu has been called ${args['origin']}.`
+    );
+  },
+});
 ```
 <!-- prettier-ignore-end -->
 
-You can add the `IMainMenu` in the `requires:` property such that it is injected into
-the `activate` function. The extension looks like:
+Now that the command is defined, you need to add the definition of the menu
+in the plugin settings file.
+
+> Tip: when using the cookiecutter template for JupyterLab extension, you can
+> request initial settings to be created.
+
+The creation of a settings file is described in the [settings example](../settings/README.md). Here only the needed keys to add a menu will describe.
 
 <!-- prettier-ignore-start -->
-```ts
-// src/index.ts#L15-L57
-
-const extension: JupyterFrontEndPlugin<void> = {
-  id: 'main-menu',
-  autoStart: true,
-  requires: [ICommandPalette, IMainMenu],
-  activate: (
-    app: JupyterFrontEnd,
-    palette: ICommandPalette,
-    mainMenu: IMainMenu
-  ) => {
-    const { commands } = app;
-
-    // Add a command
-    const command = 'jlab-examples:main-menu';
-    commands.addCommand(command, {
-      label: 'Execute jlab-examples:main-menu Command',
-      caption: 'Execute jlab-examples:main-menu Command',
-      execute: (args: any) => {
-        console.log(
-          `jlab-examples:main-menu has been called ${args['origin']}.`
-        );
-        window.alert(
-          `jlab-examples:main-menu has been called ${args['origin']}.`
-        );
-      },
-    });
-
-    // Add the command to the command palette
-    const category = 'Extension Examples';
-    palette.addItem({
-      command,
-      category,
-      args: { origin: 'from the palette' },
-    });
-
-    // Create a menu
-    const tutorialMenu: Menu = new Menu({ commands });
-    tutorialMenu.title.label = 'Main Menu Example';
-    mainMenu.addMenu(tutorialMenu, { rank: 80 });
-
-    // Add the command to the menu
-    tutorialMenu.addItem({ command, args: { origin: 'from the menu' } });
-  },
-};
+
+```json5
+// schema/plugin.json#L4-L20
+
+"jupyter.lab.menus": {
+  "main": [
+    {
+      "id": "jp-mainmenu-example-menu",
+      "label": "Main Menu Example",
+      "rank": 80,
+      "items": [
+        {
+          "command": "jlab-examples:main-menu",
+          "args": {
+            "origin": "from the menu"
+          }
+        }
+      ]
+    }
+  ]
+},
 ```
-<!-- prettier-ignore-end -->
 
-In this extension, you have the dependencies to _@jupyterlab/mainmenu_ and
-_@lumino/widgets_. Before it builds, this dependencies have to be added to the
-`package.json` file. This is done invoking the following command:
+<!-- prettier-ignore-start -->
 
-```bash
-jlpm add @jupyterlab/mainmenu @lumino/widgets
-```
+Main menu can be added and edited through the `main` property of the special 
+key `jupyter.lab.menus`. That property accepts a list of menus; each item will
+have an entry in the main menu bar.
 
-After the execution of that command, `package.json` should list them in the
-`dependencies`:
+A menu is described by:
 
-```json5
-// package.json#L50-L54
+- an `id`: Unique menu identifier
+- a `label`: The text to be displayed in the menu bar
+- some `items`: The commands available under that menu
+- a `rank`: (optional) number to order the menu items in the menu bar
 
-"dependencies": {
-  "@jupyterlab/application": "^3.1.0",
-  "@jupyterlab/mainmenu": "^3.1.0",
-  "@lumino/widgets": "^1.19.0"
-},
+The `items` are a list of objects with the following attributes:
+
+- `type`: Type of the item
+  - _command_: (default) If the item triggers a command
+  - _separator_: if the item is a menu separator
+  - _submenu_: if the item is a submenu
+- `command`: (needed for _command_ type item) the unique command id to be trigger by the menu entry
+- `args`: (optional for _command_ item) arguments to be passed to the _command_
+- `rank`: (optional) number to order the menu entries
+
+The label displayed for a command will be given by the `label` attribute
+coded in Typescript; in this example:
+
+<!-- prettier-ignore-start -->
+```ts
+// src/index.ts#L21-L21
+
+label: 'Execute jlab-examples:main-menu Command',
 ```
+<!-- prettier-ignore-end -->
+
+To add items to an existing menu, you will need to use the `id` of the default menu.
+An example to add an item to the _New_ submenu of the _File_ menu is available in the
+[launcher example](../launcher/schema/plugin.json).
+
+The list of default menu `id`s is available in the [documentation](https://jupyterlab.readthedocs.io/en/stable/extension/extension_points.html#settings-defined-menu).
+
+> See also the [documentation](https://jupyterlab.readthedocs.io/en/stable/extension/extension_points.html#settings-defined-menu).
 
 With this extension installed, a new menu _Main Menu Example_ should be present. And when
 clicking on the menu item _jlab-examples:main-menu_, the following text should appear
diff --git a/main-menu/package.json b/main-menu/package.json
index 2630287d..d95aba49 100644
--- a/main-menu/package.json
+++ b/main-menu/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -49,8 +49,7 @@
   },
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
-    "@lumino/widgets": "^1.19.0"
+    "@jupyterlab/apputils": "^3.1.0"
   },
   "devDependencies": {
     "@jupyterlab/builder": "^3.1.0",
@@ -72,7 +71,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_main_menu/labextension"
+    "outputDir": "jupyterlab_examples_main_menu/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/main-menu/schema/plugin.json b/main-menu/schema/plugin.json
new file mode 100644
index 00000000..79a5e765
--- /dev/null
+++ b/main-menu/schema/plugin.json
@@ -0,0 +1,23 @@
+{
+  "title": "Main Menu Example",
+  "description": "Main Menu Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-menu",
+        "label": "Main Menu Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "jlab-examples:main-menu",
+            "args": {
+              "origin": "from the menu"
+            }
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/main-menu/src/index.ts b/main-menu/src/index.ts
index d9f27c60..3646c8f8 100644
--- a/main-menu/src/index.ts
+++ b/main-menu/src/index.ts
@@ -3,10 +3,6 @@ import {
   JupyterFrontEndPlugin,
 } from '@jupyterlab/application';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
-import { Menu } from '@lumino/widgets';
-
 import { ICommandPalette } from '@jupyterlab/apputils';
 
 /**
@@ -15,12 +11,8 @@ import { ICommandPalette } from '@jupyterlab/apputils';
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'main-menu',
   autoStart: true,
-  requires: [ICommandPalette, IMainMenu],
-  activate: (
-    app: JupyterFrontEnd,
-    palette: ICommandPalette,
-    mainMenu: IMainMenu
-  ) => {
+  requires: [ICommandPalette],
+  activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
     const { commands } = app;
 
     // Add a command
@@ -45,14 +37,6 @@ const extension: JupyterFrontEndPlugin<void> = {
       category,
       args: { origin: 'from the palette' },
     });
-
-    // Create a menu
-    const tutorialMenu: Menu = new Menu({ commands });
-    tutorialMenu.title.label = 'Main Menu Example';
-    mainMenu.addMenu(tutorialMenu, { rank: 80 });
-
-    // Add the command to the menu
-    tutorialMenu.addItem({ command, args: { origin: 'from the menu' } });
   },
 };
 
diff --git a/react-widget/package.json b/react-widget/package.json
index 3e359f67..d5ed7fa2 100644
--- a/react-widget/package.json
+++ b/react-widget/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/server-extension/README.md b/server-extension/README.md
index 65642cbe..86be73ce 100644
--- a/server-extension/README.md
+++ b/server-extension/README.md
@@ -719,7 +719,7 @@ user about that dependency by adding the `discovery` metadata to your `package.j
 file:
 
 ```json5
-// package.json#L75-L85
+// package.json#L74-L84
 
 "jupyterlab": {
   "discovery": {
@@ -737,7 +737,7 @@ file:
 In this example, the extension requires a `server` extension:
 
 ```json5
-// package.json#L76-L76
+// package.json#L75-L75
 
 "discovery": {
 ```
@@ -745,7 +745,7 @@ In this example, the extension requires a `server` extension:
 And that server extension is available through `pip`:
 
 ```json5
-// package.json#L77-L79
+// package.json#L76-L78
 
 "server": {
   "managers": [
diff --git a/server-extension/package.json b/server-extension/package.json
index c2e8752b..22250314 100644
--- a/server-extension/package.json
+++ b/server-extension/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/settings/README.md b/settings/README.md
index b67e5fba..bc8c4d0f 100644
--- a/settings/README.md
+++ b/settings/README.md
@@ -29,17 +29,14 @@ To see how you can access the settings, let's have a look at
 `src/index.ts`.
 
 ```ts
-// src/index.ts#L19-L27
+// src/index.ts#L15-L20
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: PLUGIN_ID,
   autoStart: true,
-  requires: [IMainMenu, ISettingRegistry],
-  activate: (
-    app: JupyterFrontEnd,
-    mainMenu: IMainMenu,
-    settings: ISettingRegistry
-  ) => {
+  requires: [ISettingRegistry],
+  activate: (app: JupyterFrontEnd, settings: ISettingRegistry) => {
+    const { commands } = app;
 ```
 
 The `ISettingRegistry` is passed to the `activate` function as an
@@ -59,7 +56,20 @@ In the example it is called `schema/settings-example.json`.
 {
   "title": "Settings Example",
   "description": "Settings of the settings example.",
-  "type": "object",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-settings",
+        "label": "Settings Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "@jupyterlab-examples/settings:toggle-flag"
+          }
+        ]
+      }
+    ]
+  },
   "properties": {
     "limit": {
       "type": "integer",
@@ -73,7 +83,9 @@ In the example it is called `schema/settings-example.json`.
       "description": "This is an example of a boolean setting.",
       "default": false
     }
-  }
+  },
+  "additionalProperties": false,
+  "type": "object"
 }
 
 ```
@@ -85,6 +97,11 @@ those settings. The _type_ is a mandatory key required by [JSON Schema](https://
 For all extensions, this will be an `object` as the settings are defined
 by a _dictionary_. Then the most important entry is `properties` describing a mapping of _setting id_ and the associated properties.
 
+> JupyterLab is using special keys `jupyter.lab.xyz` to allow extension to add
+> some features to the UI for a data description. In the above example, the
+> `jupyter.lab.menus` key allow to add menu entry for your extension in the
+> main menu bar. Have a look at the [menu example](../main-menu/README.md) for more information.
+
 The naming of the file needs to follow a strict convention. When using
 settings, your extension name must be structured as _package name_**:**_settings name_. The settings file must be named _settings name_. In the example, the package name is:
 
@@ -100,7 +117,7 @@ and the extension id is:
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L12-L12
+// src/index.ts#L8-L8
 
 const PLUGIN_ID = '@jupyterlab-examples/settings:settings-example';
 ```
@@ -113,7 +130,7 @@ the `package.json` file in the `jupyterlab` section (here `schema`):
 
 <!-- prettier-ignore-start -->
 ```json5
-// package.json#L75-L79
+// package.json#L73-L77
 
 "jupyterlab": {
   "extension": true,
@@ -126,13 +143,12 @@ the `package.json` file in the `jupyterlab` section (here `schema`):
 And you should not forget to add it to the files of the package:
 
 ```json5
-// package.json#L19-L24
+// package.json#L19-L23
 
 "files": [
   "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
   "schema/**/*.json",
-  "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-  "style/index.js"
+  "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
 ],
 ```
 
@@ -141,17 +157,13 @@ use them inside your extension. Let's look at this example:
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L19-L97
+// src/index.ts#L15-L79
 
 const extension: JupyterFrontEndPlugin<void> = {
   id: PLUGIN_ID,
   autoStart: true,
-  requires: [IMainMenu, ISettingRegistry],
-  activate: (
-    app: JupyterFrontEnd,
-    mainMenu: IMainMenu,
-    settings: ISettingRegistry
-  ) => {
+  requires: [ISettingRegistry],
+  activate: (app: JupyterFrontEnd, settings: ISettingRegistry) => {
     const { commands } = app;
     let limit = 25;
     let flag = false;
@@ -204,16 +216,6 @@ const extension: JupyterFrontEndPlugin<void> = {
               });
           },
         });
-
-        // Create a menu
-        const settingsMenu = new Menu({ commands });
-        settingsMenu.title.label = 'Settings Example';
-        mainMenu.addMenu(settingsMenu, { rank: 80 });
-
-        // Add the command to the menu
-        settingsMenu.addItem({
-          command: COMMAND_ID,
-        });
       })
       .catch((reason) => {
         console.error(
@@ -230,7 +232,7 @@ your plugin settings to be loaded :
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L49-L49
+// src/index.ts#L41-L41
 
 Promise.all([app.restored, settings.load(PLUGIN_ID)])
 ```
@@ -245,7 +247,7 @@ to get its value and specify the type explicitly.
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L37-L45
+// src/index.ts#L29-L37
 
 function loadSetting(setting: ISettingRegistry.ISettings): void {
   // Read the settings and convert to the correct type
@@ -268,7 +270,7 @@ To react at a setting change by the user, you should use the signal
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L55-L55
+// src/index.ts#L47-L47
 
 setting.changed.connect(loadSetting);
 ```
@@ -279,7 +281,7 @@ the `flag` and increment the `limit` settings is implemented.
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L60-L78
+// src/index.ts#L52-L70
 
 execute: () => {
   // Programmatically change a setting
@@ -308,31 +310,38 @@ new value.
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L63-L64
+// src/index.ts#L55-L56
 
 setting.set('flag', !flag),
 setting.set('limit', limit + 1),
 ```
 <!-- prettier-ignore-end -->
 
-That command can be executed by clicking on the item menu created at the end of the
-`activate` function.
+That command can be executed by clicking on the item menu defined in the settings file:
 
 <!-- prettier-ignore-start -->
-```ts
-// src/index.ts#L82-L89
-
-const settingsMenu = new Menu({ commands });
-settingsMenu.title.label = 'Settings Example';
-mainMenu.addMenu(settingsMenu, { rank: 80 });
-
-// Add the command to the menu
-settingsMenu.addItem({
-  command: COMMAND_ID,
-});
+```json5
+// schema/settings-example.json#L4-L17
+
+"jupyter.lab.menus": {
+  "main": [
+    {
+      "id": "jp-mainmenu-example-settings",
+      "label": "Settings Example",
+      "rank": 80,
+      "items": [
+        {
+          "command": "@jupyterlab-examples/settings:toggle-flag"
+        }
+      ]
+    }
+  ]
+},
 ```
 <!-- prettier-ignore-end -->
 
+> Have a look at the [menu example](../main-menu/README.md) for more information.
+
 Note
 
 > This example uses [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
diff --git a/settings/package.json b/settings/package.json
index 2384207e..6711f179 100644
--- a/settings/package.json
+++ b/settings/package.json
@@ -19,8 +19,7 @@
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
     "schema/**/*.json",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -50,7 +49,6 @@
   },
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/settingregistry": "^3.1.0",
     "@lumino/widgets": "^1.19.0"
   },
diff --git a/settings/schema/settings-example.json b/settings/schema/settings-example.json
index 672d3577..a763da91 100644
--- a/settings/schema/settings-example.json
+++ b/settings/schema/settings-example.json
@@ -1,7 +1,20 @@
 {
   "title": "Settings Example",
   "description": "Settings of the settings example.",
-  "type": "object",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-settings",
+        "label": "Settings Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "@jupyterlab-examples/settings:toggle-flag"
+          }
+        ]
+      }
+    ]
+  },
   "properties": {
     "limit": {
       "type": "integer",
@@ -15,5 +28,7 @@
       "description": "This is an example of a boolean setting.",
       "default": false
     }
-  }
+  },
+  "additionalProperties": false,
+  "type": "object"
 }
diff --git a/settings/src/index.ts b/settings/src/index.ts
index 41d83854..1efa0387 100644
--- a/settings/src/index.ts
+++ b/settings/src/index.ts
@@ -5,10 +5,6 @@ import {
 
 import { ISettingRegistry } from '@jupyterlab/settingregistry';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
-import { Menu } from '@lumino/widgets';
-
 const PLUGIN_ID = '@jupyterlab-examples/settings:settings-example';
 
 const COMMAND_ID = '@jupyterlab-examples/settings:toggle-flag';
@@ -19,12 +15,8 @@ const COMMAND_ID = '@jupyterlab-examples/settings:toggle-flag';
 const extension: JupyterFrontEndPlugin<void> = {
   id: PLUGIN_ID,
   autoStart: true,
-  requires: [IMainMenu, ISettingRegistry],
-  activate: (
-    app: JupyterFrontEnd,
-    mainMenu: IMainMenu,
-    settings: ISettingRegistry
-  ) => {
+  requires: [ISettingRegistry],
+  activate: (app: JupyterFrontEnd, settings: ISettingRegistry) => {
     const { commands } = app;
     let limit = 25;
     let flag = false;
@@ -77,16 +69,6 @@ const extension: JupyterFrontEndPlugin<void> = {
               });
           },
         });
-
-        // Create a menu
-        const settingsMenu = new Menu({ commands });
-        settingsMenu.title.label = 'Settings Example';
-        mainMenu.addMenu(settingsMenu, { rank: 80 });
-
-        // Add the command to the menu
-        settingsMenu.addItem({
-          command: COMMAND_ID,
-        });
       })
       .catch((reason) => {
         console.error(
diff --git a/signals/package.json b/signals/package.json
index df3280f3..0bced541 100644
--- a/signals/package.json
+++ b/signals/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -51,7 +51,6 @@
     "@jupyterlab/application": "^3.1.0",
     "@jupyterlab/apputils": "^3.1.0",
     "@jupyterlab/launcher": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@jupyterlab/translation": "^3.1.0",
     "@lumino/algorithm": "^1.3.3",
     "@lumino/coreutils": "^1.5.3",
@@ -80,7 +79,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_signals/labextension"
+    "outputDir": "jupyterlab_examples_signals/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/signals/schema/plugin.json b/signals/schema/plugin.json
new file mode 100644
index 00000000..21f3a7fe
--- /dev/null
+++ b/signals/schema/plugin.json
@@ -0,0 +1,20 @@
+{
+  "title": "Signal Example",
+  "description": "Signal Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-signal",
+        "label": "Signal Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "examples-signals:create"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/signals/src/index.ts b/signals/src/index.ts
index 20ebc6de..7d7e5053 100644
--- a/signals/src/index.ts
+++ b/signals/src/index.ts
@@ -7,12 +7,8 @@ import { ICommandPalette } from '@jupyterlab/apputils';
 
 import { ILauncher } from '@jupyterlab/launcher';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
 import { ITranslator } from '@jupyterlab/translation';
 
-import { Menu } from '@lumino/widgets';
-
 import { SignalExamplePanel } from './panel';
 
 /**
@@ -29,7 +25,7 @@ const extension: JupyterFrontEndPlugin<void> = {
   id: 'signals',
   autoStart: true,
   optional: [ILauncher],
-  requires: [ICommandPalette, IMainMenu, ITranslator],
+  requires: [ICommandPalette, ITranslator],
   activate,
 };
 
@@ -38,14 +34,12 @@ const extension: JupyterFrontEndPlugin<void> = {
  *
  * @param app Jupyter Font End
  * @param palette Jupyter Commands Palette
- * @param mainMenu Jupyter Menu
  * @param translator Jupyter Translator
  * @param launcher [optional] Jupyter Launcher
  */
 function activate(
   app: JupyterFrontEnd,
   palette: ICommandPalette,
-  mainMenu: IMainMenu,
   translator: ITranslator,
   launcher: ILauncher | null
 ): void {
@@ -76,11 +70,6 @@ function activate(
     });
   }
 
-  // Add menu tab
-  const signalMenu = new Menu({ commands });
-  signalMenu.title.label = trans.__('Signal Example');
-  mainMenu.addMenu(signalMenu);
-
   // Add commands to registry
   commands.addCommand(CommandIDs.create, {
     label: trans.__('Open the Signal Example Panel'),
@@ -90,7 +79,6 @@ function activate(
 
   // Add items in command palette and menu
   palette.addItem({ command: CommandIDs.create, category });
-  signalMenu.addItem({ command: CommandIDs.create });
 }
 
 export default extension;
diff --git a/state/package.json b/state/package.json
index e94cde33..23f1148d 100644
--- a/state/package.json
+++ b/state/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/toolbar-button/package.json b/toolbar-button/package.json
index 0ad6816a..e90317e1 100644
--- a/toolbar-button/package.json
+++ b/toolbar-button/package.json
@@ -18,8 +18,7 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,.js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,.js,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/widgets/README.md b/widgets/README.md
index 1a1fdaf1..d2440f16 100644
--- a/widgets/README.md
+++ b/widgets/README.md
@@ -16,9 +16,9 @@ It is the fundamental brick of any visual component in the JupyterLab interface.
 The base widget class can be imported with:
 
 ```ts
-// src/index.ts#L10-L10
+// src/index.ts#L8-L8
 
-import { Menu, Widget } from '@lumino/widgets';
+import { Widget } from '@lumino/widgets';
 ```
 
 It requires to add the library as package dependency:
@@ -34,7 +34,7 @@ Inside of the `activate` function, you can obtain it through the `shell` attribu
 of the `app` object:
 
 ```ts
-// src/index.ts#L24-L24
+// src/index.ts#L18-L18
 
 const { commands, shell } = app;
 ```
@@ -44,7 +44,7 @@ in this example:
 
 <!-- prettier-ignore-start -->
 ```ts
-// src/index.ts#L30-L33
+// src/index.ts#L24-L27
 
 execute: () => {
   const widget = new ExampleWidget();
@@ -63,7 +63,7 @@ In this case, no specific behavior is defined for the widget. Only some properti
 - `title.closable`: Allow the widget tab to be closed
 
 ```ts
-// src/index.ts#L47-L55
+// src/index.ts#L35-L43
 
 class ExampleWidget extends Widget {
   constructor() {
diff --git a/widgets/package.json b/widgets/package.json
index 0a71231f..d0e5a29a 100644
--- a/widgets/package.json
+++ b/widgets/package.json
@@ -18,8 +18,8 @@
   },
   "files": [
     "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
+    "style/**/*.{css,eot,js,gif,html,jpg,json,png,svg,woff2,ttf}",
+    "schema/**/*.json"
   ],
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -49,7 +49,6 @@
   },
   "dependencies": {
     "@jupyterlab/application": "^3.1.0",
-    "@jupyterlab/mainmenu": "^3.1.0",
     "@lumino/algorithm": "^1.3.3",
     "@lumino/coreutils": "^1.5.3",
     "@lumino/disposable": "^1.4.3"
@@ -74,7 +73,8 @@
   ],
   "jupyterlab": {
     "extension": true,
-    "outputDir": "jupyterlab_examples_widgets/labextension"
+    "outputDir": "jupyterlab_examples_widgets/labextension",
+    "schemaDir": "schema"
   },
   "styleModule": "style/index.js"
 }
diff --git a/widgets/schema/plugin.json b/widgets/schema/plugin.json
new file mode 100644
index 00000000..32ea9ec5
--- /dev/null
+++ b/widgets/schema/plugin.json
@@ -0,0 +1,20 @@
+{
+  "title": "Widget Example",
+  "description": "Widget Example settings.",
+  "jupyter.lab.menus": {
+    "main": [
+      {
+        "id": "jp-mainmenu-example-widget",
+        "label": "Widget Example",
+        "rank": 80,
+        "items": [
+          {
+            "command": "widgets:open-tab"
+          }
+        ]
+      }
+    ]
+  },
+  "additionalProperties": false,
+  "type": "object"
+}
diff --git a/widgets/src/index.ts b/widgets/src/index.ts
index b2d7b1d6..a42858dc 100644
--- a/widgets/src/index.ts
+++ b/widgets/src/index.ts
@@ -5,9 +5,7 @@ import {
 
 import { ICommandPalette } from '@jupyterlab/apputils';
 
-import { IMainMenu } from '@jupyterlab/mainmenu';
-
-import { Menu, Widget } from '@lumino/widgets';
+import { Widget } from '@lumino/widgets';
 
 /**
  * Activate the widgets example extension.
@@ -15,12 +13,8 @@ import { Menu, Widget } from '@lumino/widgets';
 const extension: JupyterFrontEndPlugin<void> = {
   id: 'widgets-example',
   autoStart: true,
-  requires: [ICommandPalette, IMainMenu],
-  activate: (
-    app: JupyterFrontEnd,
-    palette: ICommandPalette,
-    mainMenu: IMainMenu
-  ) => {
+  requires: [ICommandPalette],
+  activate: (app: JupyterFrontEnd, palette: ICommandPalette) => {
     const { commands, shell } = app;
     const command = 'widgets:open-tab';
 
@@ -33,12 +27,6 @@ const extension: JupyterFrontEndPlugin<void> = {
       },
     });
     palette.addItem({ command, category: 'Extension Examples' });
-
-    const exampleMenu = new Menu({ commands });
-
-    exampleMenu.title.label = 'Widget Example';
-    mainMenu.addMenu(exampleMenu, { rank: 80 });
-    exampleMenu.addItem({ command });
   },
 };
 


Test patch:
diff --git a/command-palette/ui-tests/README.md b/command-palette/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/command-palette/ui-tests/README.md
+++ b/command-palette/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/commands/ui-tests/README.md b/commands/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/commands/ui-tests/README.md
+++ b/commands/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/completer/ui-tests/README.md b/completer/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/completer/ui-tests/README.md
+++ b/completer/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/context-menu/ui-tests/README.md b/context-menu/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/context-menu/ui-tests/README.md
+++ b/context-menu/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/context-menu/ui-tests/tests/context-menu.spec.ts b/context-menu/ui-tests/tests/context-menu.spec.ts
index a21a9834..7727807f 100644
--- a/context-menu/ui-tests/tests/context-menu.spec.ts
+++ b/context-menu/ui-tests/tests/context-menu.spec.ts
@@ -15,10 +15,8 @@ test('should have new context menu for example files', async ({ page }) => {
 
   // Click li[role="menuitem"]:has-text("File")
   await page.click('li[role="menuitem"]:has-text("File")');
-
   // Click ul[role="menu"] >> text=New
   await page.click('ul[role="menu"] >> text=New');
-
   // Click #jp-mainmenu-file-new >> text=Text File
   await page.click('#jp-mainmenu-file-new >> text=Text File');
 
@@ -36,6 +34,9 @@ test('should have new context menu for example files', async ({ page }) => {
   // Press Enter
   await page.press('input.jp-DirListing-editor', 'Enter');
 
+  // Wait for the data attribute to be set
+  await page.waitForTimeout(200);
+
   // Click [aria-label="File Browser Section"] >> text=test.example
   await page.click('[aria-label="File Browser Section"] >> text=test.example', {
     button: 'right',
diff --git a/custom-log-console/ui-tests/README.md b/custom-log-console/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/custom-log-console/ui-tests/README.md
+++ b/custom-log-console/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/datagrid/ui-tests/README.md b/datagrid/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/datagrid/ui-tests/README.md
+++ b/datagrid/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/documents/ui-tests/README.md b/documents/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/documents/ui-tests/README.md
+++ b/documents/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/documents/ui-tests/tests/documents.spec.ts b/documents/ui-tests/tests/documents.spec.ts
index 6fba8822..94f1b91c 100644
--- a/documents/ui-tests/tests/documents.spec.ts
+++ b/documents/ui-tests/tests/documents.spec.ts
@@ -9,10 +9,8 @@ test('should check if the cube is loaded', async ({ page }) => {
 
   // Click text=File
   await page.click('text=File');
-
   // Click ul[role="menu"] >> text=New
   await page.click('ul[role="menu"] >> text=New');
-
   // Click #jp-mainmenu-file-new >> text=Text File
   page.click('#jp-mainmenu-file-new >> text=Text File');
 
@@ -28,10 +26,10 @@ test('should check if the cube is loaded', async ({ page }) => {
   // Press s with modifiers
   await page.keyboard.press('Control+s');
 
-  // Click div[role="main"] >> text=Launcheruntitled.txt >> :nth-match(svg, 4)
-  await page.click(
-    'div[role="main"] >> text=Launcheruntitled.txt >> :nth-match(svg, 4)'
-  );
+  // Close file
+  await page.click('text=File');
+  // Click ul[role="menu"] >> text=New
+  await page.click('ul[role="menu"] >> text=Close Tab');
 
   // Click [aria-label="File Browser Section"] >> text=untitled.txt
   await page.click('[aria-label="File Browser Section"] >> text=untitled.txt', {
@@ -78,5 +76,7 @@ test('should check if the cube is loaded', async ({ page }) => {
   await page.keyboard.press('Control+s');
 
   // Compare screenshot with a stored reference.
-  expect(await page.screenshot()).toMatchSnapshot('documents-example.png');
+  expect(
+    await (await page.waitForSelector('div[role="main"]')).screenshot()
+  ).toMatchSnapshot('documents-example.png');
 });
diff --git a/documents/ui-tests/tests/documents.spec.ts-snapshots/documents-example-linux.png b/documents/ui-tests/tests/documents.spec.ts-snapshots/documents-example-linux.png
index cddf0ae6..8dd57646 100644
Binary files a/documents/ui-tests/tests/documents.spec.ts-snapshots/documents-example-linux.png and b/documents/ui-tests/tests/documents.spec.ts-snapshots/documents-example-linux.png differ
diff --git a/hello-world/ui-tests/README.md b/hello-world/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/hello-world/ui-tests/README.md
+++ b/hello-world/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/kernel-messaging/ui-tests/README.md b/kernel-messaging/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/kernel-messaging/ui-tests/README.md
+++ b/kernel-messaging/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/kernel-output/ui-tests/README.md b/kernel-output/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/kernel-output/ui-tests/README.md
+++ b/kernel-output/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/launcher/ui-tests/README.md b/launcher/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/launcher/ui-tests/README.md
+++ b/launcher/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/log-messages/ui-tests/README.md b/log-messages/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/log-messages/ui-tests/README.md
+++ b/log-messages/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/main-menu/ui-tests/README.md b/main-menu/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/main-menu/ui-tests/README.md
+++ b/main-menu/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/react-widget/ui-tests/README.md b/react-widget/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/react-widget/ui-tests/README.md
+++ b/react-widget/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/scripts/run_all_ui_tests.sh b/scripts/run_all_ui_tests.sh
index b37d7939..f766322f 100755
--- a/scripts/run_all_ui_tests.sh
+++ b/scripts/run_all_ui_tests.sh
@@ -5,7 +5,7 @@ HERE=$(dirname $0)
 ## To update all screenshots run ./scripts/run_all_ui_tests.sh npx playwright test -u
 COMMAND=${*:-npx playwright test}
 
-declare -a extensions=("command-palette" "commands" "completer" "context-menu" "custom-log-console" "datagrid" "hello-world" "kernel-messaging" "kernel-output" "launcher" "log-messages" "main-menu" "react-widget" "settings" "server-extension" "signals" "state" "toolbar-button" "widgets")
+declare -a extensions=("command-palette" "commands" "completer" "context-menu" "custom-log-console" "datagrid" "documents" "hello-world" "kernel-messaging" "kernel-output" "launcher" "log-messages" "main-menu" "react-widget" "settings" "server-extension" "signals" "state" "toolbar-button" "widgets")
 
 # Build docker image
 docker-compose -f $HERE/../end-to-end-tests/docker-compose.yml build --no-cache
diff --git a/server-extension/ui-tests/README.md b/server-extension/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/server-extension/ui-tests/README.md
+++ b/server-extension/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/settings/ui-tests/README.md b/settings/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/settings/ui-tests/README.md
+++ b/settings/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/signals/ui-tests/README.md b/signals/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/signals/ui-tests/README.md
+++ b/signals/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/state/ui-tests/README.md b/state/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/state/ui-tests/README.md
+++ b/state/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/toolbar-button/ui-tests/README.md b/toolbar-button/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/toolbar-button/ui-tests/README.md
+++ b/toolbar-button/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests
diff --git a/widgets/ui-tests/README.md b/widgets/ui-tests/README.md
index d02f6784..d2be7291 100644
--- a/widgets/ui-tests/README.md
+++ b/widgets/ui-tests/README.md
@@ -19,7 +19,7 @@ jlpm run build:prod
 2. Execute the docker stack in the example folder:
 
 ```
-docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build
+docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build --no-cache
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e
 docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down
 ```
@@ -107,7 +107,7 @@ docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.
 jupyter lab --ServerApp.token= --ServerApp.password=
 ```
 
-3. Launch the code generator tool:
+3. Launch the debug tool:
 
 ```
 cd ui-tests