diff --git a/README.md b/README.md
index 81031d77c..cf161ab5e 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,37 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
### Node developers API
+
+
+ v1.3.1: Extension API to register custom topbar menu items
+
+ Extensions can call the following API to register custom topbar menu items.
+
+```js
+ app.extensionManager.menu.registerTopbarCommands(["ext", "ext2"], [{id:"foo", label: "foo", function: () => alert(1)}])
+```
+
+
+
+
+
+ v1.2.27: Extension API to add toast message
i
+
+ Extensions can call the following API to add toast messages.
+
+```js
+ app.extensionManager.toast.add({
+ severity: 'info',
+ summary: 'Loaded!',
+ detail: 'Extension loaded!',
+ life: 3000
+ })
+```
+Documentation of all supported options can be found here:
+
+
+
+
v1.2.4: Extension API to register custom sidebar tab
@@ -162,24 +193,6 @@ We will support custom icons later.

-
- v1.2.27: Extension API to add toast message
-
- Extensions can call the following API to add toast messages.
-
-```js
- app.extensionManager.toast.add({
- severity: 'info',
- summary: 'Loaded!',
- detail: 'Extension loaded!',
- life: 3000
- })
-```
-Documentation of all supported options can be found here:
-
-
-
-
## Road Map
### What has been done
diff --git a/browser_tests/ComfyPage.ts b/browser_tests/ComfyPage.ts
index 72f51aab6..93ce93550 100644
--- a/browser_tests/ComfyPage.ts
+++ b/browser_tests/ComfyPage.ts
@@ -229,6 +229,32 @@ class Topbar {
.locator('.workflow-tabs .workflow-label')
.allInnerTexts()
}
+
+ async triggerTopbarCommand(path: string[]) {
+ if (path.length < 2) {
+ throw new Error('Path is too short')
+ }
+
+ const tabName = path[0]
+ const topLevelMenu = this.page.locator(
+ `.top-menubar .p-menubar-item:has-text("${tabName}")`
+ )
+ await topLevelMenu.waitFor({ state: 'visible' })
+ await topLevelMenu.click()
+
+ for (let i = 1; i < path.length; i++) {
+ const commandName = path[i]
+ const menuItem = this.page.locator(
+ `.top-menubar .p-menubar-submenu .p-menubar-item:has-text("${commandName}")`
+ )
+ await menuItem.waitFor({ state: 'visible' })
+ await menuItem.hover()
+
+ if (i === path.length - 1) {
+ await menuItem.click()
+ }
+ }
+ }
}
class ComfyMenu {
diff --git a/browser_tests/extensionAPI.spec.ts b/browser_tests/extensionAPI.spec.ts
new file mode 100644
index 000000000..8002bda53
--- /dev/null
+++ b/browser_tests/extensionAPI.spec.ts
@@ -0,0 +1,32 @@
+import { expect } from '@playwright/test'
+import { comfyPageFixture as test } from './ComfyPage'
+
+test.describe('Topbar commands', () => {
+ test.beforeEach(async ({ comfyPage }) => {
+ await comfyPage.setSetting('Comfy.UseNewMenu', 'Floating')
+ })
+
+ test.afterEach(async ({ comfyPage }) => {
+ await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
+ })
+
+ test('Should allow registering topbar commands', async ({ comfyPage }) => {
+ await comfyPage.page.evaluate(() => {
+ window['app'].extensionManager.menu.registerTopbarCommands(
+ ['ext'],
+ [
+ {
+ id: 'foo',
+ label: 'foo',
+ function: () => {
+ window['foo'] = true
+ }
+ }
+ ]
+ )
+ })
+
+ await comfyPage.menu.topbar.triggerTopbarCommand(['ext', 'foo'])
+ expect(await comfyPage.page.evaluate(() => window['foo'])).toBe(true)
+ })
+})
diff --git a/src/components/appMenu/AppMenu.vue b/src/components/appMenu/AppMenu.vue
index 035c617ea..8f78ec693 100644
--- a/src/components/appMenu/AppMenu.vue
+++ b/src/components/appMenu/AppMenu.vue
@@ -29,7 +29,7 @@
icon="pi pi-times"
:severity="executingPrompt ? 'danger' : 'secondary'"
:disabled="!executingPrompt"
- @click="() => commandStore.getCommand('Comfy.Interrupt')()"
+ @click="() => commandStore.getCommandFunction('Comfy.Interrupt')()"
>