diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
index 5a0b5221..fdab829f 100644
--- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
+++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
@@ -302,6 +302,7 @@ import { CoreErrorHandler } from './contributions/core-error-handler';
 import { CompilerErrors } from './contributions/compiler-errors';
 import { WidgetManager } from './theia/core/widget-manager';
 import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
+import { StartupTask } from './widgets/sketchbook/startup-task';
 
 MonacoThemingService.register({
   id: 'arduino-theme',
@@ -698,6 +699,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
   Contribution.configure(bind, PlotterFrontendContribution);
   Contribution.configure(bind, Format);
   Contribution.configure(bind, CompilerErrors);
+  Contribution.configure(bind, StartupTask);
 
   // Disabled the quick-pick customization from Theia when multiple formatters are available.
   // Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts
index e49da551..3ba955c7 100644
--- a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts
+++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts
@@ -9,7 +9,10 @@ import { FrontendApplication } from '@theia/core/lib/browser/frontend-applicatio
 import { FocusTracker, Widget } from '@theia/core/lib/browser';
 import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
 import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
-import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
+import {
+  WorkspaceInput,
+  WorkspaceService as TheiaWorkspaceService,
+} from '@theia/workspace/lib/browser/workspace-service';
 import { ConfigService } from '../../../common/protocol/config-service';
 import {
   SketchesService,
@@ -17,6 +20,8 @@ import {
 } from '../../../common/protocol/sketches-service';
 import { BoardsServiceProvider } from '../../boards/boards-service-provider';
 import { BoardsConfig } from '../../boards/boards-config';
+import { FileStat } from '@theia/filesystem/lib/common/files';
+import { StartupTask } from '../../widgets/sketchbook/startup-task';
 
 @injectable()
 export class WorkspaceService extends TheiaWorkspaceService {
@@ -82,13 +87,75 @@ export class WorkspaceService extends TheiaWorkspaceService {
     }
   }
 
-  protected override openNewWindow(workspacePath: string): void {
+  /**
+   * Copied from Theia as-is to be able to pass the original `options` down.
+   */
+  protected override async doOpen(
+    uri: URI,
+    options?: WorkspaceInput
+  ): Promise<URI | undefined> {
+    const stat = await this.toFileStat(uri);
+    if (stat) {
+      if (!stat.isDirectory && !this.isWorkspaceFile(stat)) {
+        const message = `Not a valid workspace: ${uri.path.toString()}`;
+        this.messageService.error(message);
+        throw new Error(message);
+      }
+      // The same window has to be preserved too (instead of opening a new one), if the workspace root is not yet available and we are setting it for the first time.
+      // Option passed as parameter has the highest priority (for api developers), then the preference, then the default.
+      await this.roots;
+      const { preserveWindow } = {
+        preserveWindow:
+          this.preferences['workspace.preserveWindow'] || !this.opened,
+        ...options,
+      };
+      await this.server.setMostRecentlyUsedWorkspace(uri.toString());
+      if (preserveWindow) {
+        this._workspace = stat;
+      }
+      this.openWindow(stat, Object.assign(options ?? {}, { preserveWindow })); // Unlike Theia, IDE2 passes the whole `input` downstream and not only { preserveWindow }
+      return;
+    }
+    throw new Error(
+      'Invalid workspace root URI. Expected an existing directory or workspace file.'
+    );
+  }
+
+  /**
+   * Copied from Theia. Can pass the `options` further down the chain.
+   */
+  protected override openWindow(uri: FileStat, options?: WorkspaceInput): void {
+    const workspacePath = uri.resource.path.toString();
+    if (this.shouldPreserveWindow(options)) {
+      this.reloadWindow();
+    } else {
+      try {
+        this.openNewWindow(workspacePath, options); // Unlike Theia, IDE2 passes the `input` downstream.
+      } catch (error) {
+        // Fall back to reloading the current window in case the browser has blocked the new window
+        this._workspace = uri;
+        this.logger.error(error.toString()).then(() => this.reloadWindow());
+      }
+    }
+  }
+
+  protected override openNewWindow(
+    workspacePath: string,
+    options?: WorkspaceInput
+  ): void {
     const { boardsConfig } = this.boardsServiceProvider;
     const url = BoardsConfig.Config.setConfig(
       boardsConfig,
       new URL(window.location.href)
     ); // Set the current boards config for the new browser window.
     url.hash = workspacePath;
+    if (StartupTask.WorkspaceInput.is(options)) {
+      url.searchParams.set(
+        StartupTask.QUERY_STRING,
+        encodeURIComponent(JSON.stringify(options.tasks))
+      );
+    }
+
     this.windowService.openNewWindow(url.toString());
   }
 
diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts
index 85e70355..54594b2d 100644
--- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts
+++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts
@@ -23,7 +23,10 @@ import {
 } from '@theia/core/lib/browser/preferences/preference-service';
 import { ArduinoMenus, PlaceholderMenuNode } from '../../menu/arduino-menus';
 import { SketchbookCommands } from '../sketchbook/sketchbook-commands';
-import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
+import {
+  CurrentSketch,
+  SketchesServiceClientImpl,
+} from '../../../common/protocol/sketches-service-client-impl';
 import { Contribution } from '../../contributions/contribution';
 import { ArduinoPreferences } from '../../arduino-preferences';
 import { MainMenuManager } from '../../../common/main-menu-manager';
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts
index f8e36065..50b5f900 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts
@@ -1,6 +1,14 @@
 import { Command } from '@theia/core/lib/common/command';
 
 export namespace SketchbookCommands {
+  export const TOGGLE_SKETCHBOOK_WIDGET: Command = {
+    id: 'arduino-sketchbook-widget:toggle',
+  };
+
+  export const REVEAL_SKETCH_NODE: Command = {
+    id: 'arduino-sketchbook--reveal-sketch-node',
+  };
+
   export const OPEN_NEW_WINDOW = Command.toLocalizedCommand(
     {
       id: 'arduino-sketchbook--open-sketch-new-window',
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts
index 16b66a26..74b78288 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts
@@ -29,6 +29,7 @@ import {
 } from '../../../common/protocol/sketches-service-client-impl';
 import { FileService } from '@theia/filesystem/lib/browser/file-service';
 import { URI } from '../../contributions/contribution';
+import { WorkspaceInput } from '@theia/workspace/lib/browser';
 
 export const SKETCHBOOK__CONTEXT = ['arduino-sketchbook--context'];
 
@@ -77,7 +78,7 @@ export class SketchbookWidgetContribution
         area: 'left',
         rank: 1,
       },
-      toggleCommandId: 'arduino-sketchbook-widget:toggle',
+      toggleCommandId: SketchbookCommands.TOGGLE_SKETCHBOOK_WIDGET.id,
       toggleKeybinding: 'CtrlCmd+Shift+B',
     });
   }
@@ -100,11 +101,12 @@ export class SketchbookWidgetContribution
 
   override registerCommands(registry: CommandRegistry): void {
     super.registerCommands(registry);
-
+    registry.registerCommand(SketchbookCommands.REVEAL_SKETCH_NODE, {
+      execute: (treeWidgetId: string, nodeUri: string) =>
+        this.revealSketchNode(treeWidgetId, nodeUri),
+    });
     registry.registerCommand(SketchbookCommands.OPEN_NEW_WINDOW, {
-      execute: async (arg) => {
-        return this.workspaceService.open(arg.node.uri);
-      },
+      execute: (arg) => this.openNewWindow(arg.node),
       isEnabled: (arg) =>
         !!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
       isVisible: (arg) =>
@@ -197,7 +199,7 @@ export class SketchbookWidgetContribution
 
     // unregister main menu action
     registry.unregisterMenuAction({
-      commandId: 'arduino-sketchbook-widget:toggle',
+      commandId: SketchbookCommands.TOGGLE_SKETCHBOOK_WIDGET.id,
     });
 
     registry.registerMenuAction(SKETCHBOOK__CONTEXT__MAIN_GROUP, {
@@ -207,6 +209,28 @@ export class SketchbookWidgetContribution
     });
   }
 
+  private openNewWindow(node: SketchbookTree.SketchDirNode): void {
+    const widget = this.tryGetWidget();
+    if (widget) {
+      const treeWidgetId = widget.activeTreeWidgetId();
+      if (!treeWidgetId) {
+        console.warn(`Could not retrieve active sketchbook tree ID.`);
+        return;
+      }
+      const nodeUri = node.uri.toString();
+      const options: WorkspaceInput = {};
+      Object.assign(options, {
+        tasks: [
+          {
+            command: SketchbookCommands.REVEAL_SKETCH_NODE.id,
+            args: [treeWidgetId, nodeUri],
+          },
+        ],
+      });
+      return this.workspaceService.open(node.uri, options);
+    }
+  }
+
   /**
    * Reveals and selects node in the file navigator to which given widget is related.
    * Does nothing if given widget undefined or doesn't have related resource.
@@ -230,4 +254,17 @@ export class SketchbookWidgetContribution
   protected onCurrentWidgetChangedHandler(): void {
     this.selectWidgetFileNode(this.shell.currentWidget);
   }
+
+  private async revealSketchNode(
+    treeWidgetId: string,
+    nodeUIri: string
+  ): Promise<void> {
+    return this.widget
+      .then((widget) => this.shell.activateWidget(widget.id))
+      .then((widget) => {
+        if (widget instanceof SketchbookWidget) {
+          return widget.revealSketchNode(treeWidgetId, nodeUIri);
+        }
+      });
+  }
 }
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx
index f0a427de..0b7b920a 100644
--- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx
@@ -1,4 +1,8 @@
-import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
+import {
+  inject,
+  injectable,
+  postConstruct,
+} from '@theia/core/shared/inversify';
 import { toArray } from '@theia/core/shared/@phosphor/algorithm';
 import { IDragEvent } from '@theia/core/shared/@phosphor/dragdrop';
 import { DockPanel, Widget } from '@theia/core/shared/@phosphor/widgets';
@@ -7,6 +11,8 @@ import { Disposable } from '@theia/core/lib/common/disposable';
 import { BaseWidget } from '@theia/core/lib/browser/widgets/widget';
 import { SketchbookTreeWidget } from './sketchbook-tree-widget';
 import { nls } from '@theia/core/lib/common';
+import { CloudSketchbookCompositeWidget } from '../cloud-sketchbook/cloud-sketchbook-composite-widget';
+import { URI } from '../../contributions/contribution';
 
 @injectable()
 export class SketchbookWidget extends BaseWidget {
@@ -45,6 +51,57 @@ export class SketchbookWidget extends BaseWidget {
     return this.localSketchbookTreeWidget;
   }
 
+  activeTreeWidgetId(): string | undefined {
+    const selectedTreeWidgets = toArray(
+      this.sketchbookTreesContainer.selectedWidgets()
+    ).map(({ id }) => id);
+    if (selectedTreeWidgets.length > 1) {
+      console.warn(
+        `Found multiple selected tree widgets: ${JSON.stringify(
+          selectedTreeWidgets
+        )}. Expected only one.`
+      );
+    }
+    return selectedTreeWidgets.shift();
+  }
+
+  async revealSketchNode(treeWidgetId: string, nodeUri: string): Promise<void> {
+    const widget = toArray(this.sketchbookTreesContainer.widgets())
+      .filter(({ id }) => id === treeWidgetId)
+      .shift();
+    if (!widget) {
+      console.warn(`Could not find tree widget with ID: ${widget}`);
+      return;
+    }
+    // TODO: remove this when the remote/local sketchbooks and their widgets are cleaned up.
+    const findTreeWidget = (
+      widget: Widget | undefined
+    ): SketchbookTreeWidget | undefined => {
+      if (widget instanceof SketchbookTreeWidget) {
+        return widget;
+      }
+      if (widget instanceof CloudSketchbookCompositeWidget) {
+        return widget.getTreeWidget();
+      }
+      return undefined;
+    };
+    const treeWidget = findTreeWidget(
+      toArray(this.sketchbookTreesContainer.widgets())
+        .filter(({ id }) => id === treeWidgetId)
+        .shift()
+    );
+    if (!treeWidget) {
+      console.warn(`Could not find tree widget with ID: ${treeWidget}`);
+      return;
+    }
+    this.sketchbookTreesContainer.activateWidget(widget);
+
+    const treeNode = await treeWidget.model.revealFile(new URI(nodeUri));
+    if (!treeNode) {
+      console.warn(`Could not find tree node with URI: ${nodeUri}`);
+    }
+  }
+
   protected override onActivateRequest(message: Message): void {
     super.onActivateRequest(message);
 
diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/startup-task.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/startup-task.ts
new file mode 100644
index 00000000..47ab60db
--- /dev/null
+++ b/arduino-ide-extension/src/browser/widgets/sketchbook/startup-task.ts
@@ -0,0 +1,42 @@
+import { injectable } from '@theia/core/shared/inversify';
+import { WorkspaceInput as TheiaWorkspaceInput } from '@theia/workspace/lib/browser';
+import { Contribution } from '../../contributions/contribution';
+
+export interface Task {
+  command: string;
+  /**
+   * This must be JSON serializable.
+   */
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  args?: any[];
+}
+
+@injectable()
+export class StartupTask extends Contribution {
+  override onReady(): void {
+    const params = new URLSearchParams(window.location.search);
+    const encoded = params.get(StartupTask.QUERY_STRING);
+    if (!encoded) return;
+
+    const commands = JSON.parse(decodeURIComponent(encoded));
+
+    if (Array.isArray(commands)) {
+      commands.forEach(({ command, args }) => {
+        this.commandService.executeCommand(command, ...args);
+      });
+    }
+  }
+}
+export namespace StartupTask {
+  export const QUERY_STRING = 'startupTasks';
+  export interface WorkspaceInput extends TheiaWorkspaceInput {
+    tasks: Task[];
+  }
+  export namespace WorkspaceInput {
+    export function is(
+      input: (TheiaWorkspaceInput & Partial<WorkspaceInput>) | undefined
+    ): input is WorkspaceInput {
+      return !!input && !!input.tasks;
+    }
+  }
+}