Skip to content

feat: add Custom Node Manager#9047

Open
Pfannkuchensack wants to merge 18 commits intoinvoke-ai:mainfrom
Pfannkuchensack:feature/custom-node-manager
Open

feat: add Custom Node Manager#9047
Pfannkuchensack wants to merge 18 commits intoinvoke-ai:mainfrom
Pfannkuchensack:feature/custom-node-manager

Conversation

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator

Summary

Adds a Custom Node Manager as a new tab in the sidebar, allowing users to install, manage, and remove community node packs directly from the UI — no manual file copying or restarts required.

Backend:

  • New API router (/api/v2/custom_nodes/) with endpoints for list, install (git clone), uninstall, and reload
  • Runtime node loading/unloading via InvocationRegistry.unregister_pack() — no restart needed
  • Automatic pip install of requirements.txt dependencies
  • Automatic detection and import of workflow .json files from node pack repos (tagged with node-pack:<name> for filtering, removed on uninstall)
  • OpenAPI schema cache invalidation so the workflow editor reflects changes immediately

Frontend:

  • New "Nodes" tab with circuit icon (PiCircuitryBold) in the sidebar between Models and Queue
  • Two-panel layout matching the Model Manager pattern:
    • Left: installed node packs list with reload and per-pack uninstall
    • Right: tabbed install UI (Git Repository URL / Scan Folder) with install log table
  • Security warning banner about trusting node pack authors
  • RTK Query endpoints with cache tag invalidation for CustomNodePacks, Schema, and Workflow

Docs:

  • User guide: docs/nodes/customNodeManager.md
  • Developer guide: docs/nodes/creatingNodePack.md (repo structure, __init__.py, workflows, best practices)

Related Issues / Discussions

N/A — new feature

QA Instructions

  1. Start the dev server (backend + frontend)
  2. Click the new Nodes tab (circuit icon) in the left sidebar
  3. Install a node pack: paste a Git URL into the "Git Repository URL" tab and click Install. Verify:
    • The pack appears in the left panel with node count and type badges
    • If the repo contains workflow .json files, they appear in the workflow library (tagged node-pack:<name>)
    • The install log at the bottom shows the result
  4. Reload: click the Reload button and verify it picks up manually added packs
  5. Uninstall: click Uninstall on a pack and verify:
    • The pack disappears from the list
    • The nodes are no longer available in the workflow editor (no restart needed)
    • Imported workflows from that pack are removed from the library
  6. Edge cases: try installing with an invalid URL, a repo without __init__.py, an already-installed pack

Merge Plan

No special considerations. No DB schema changes — workflows are stored via the existing workflow library API.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable) — 21 backend + 10 frontend tests
  • ❗Changes to a redux slice have a corresponding migration
  • Documentation added / updated (if applicable) — user guide + developer guide
  • Updated What's New copy (if doing a release after this PR)

…from the UI

Adds a new "Nodes" tab (circuit icon) to the sidebar with a two-panel layout:
- Left: list of installed custom node packs with reload and uninstall
- Right: tabbed install UI (Git URL / Scan Folder) with install log

Backend API endpoints (POST install, DELETE uninstall, POST reload, GET list)
handle git clone, pip dependency install, runtime node loading/unloading,
and automatic workflow import from node pack repositories. Workflows are
tagged with node-pack:<name> and removed on uninstall.

Includes user and developer documentation, plus 31 tests (21 backend, 10 frontend).
@github-actions github-actions bot added api python PRs that change python files invocations PRs that change invocations frontend PRs that change frontend files python-tests PRs that change python tests docs PRs that change docs labels Apr 12, 2026
@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

image image

@iwr-redmond
Copy link
Copy Markdown

One of the most annoying problems with ComfyUI's custom nodes is conflicting dependencies, which has gotten so bad that ComfyUI is now developing an isolation solution to mitigate it. Has there been any thought at this stage about how to protect the base install from incompatible dependencies in requirements.txt?

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

So far, I haven't seen many Invoke nodes that require "requirements.txt". I'd rather not make a big deal out of it while it's not too serious.

@iwr-redmond
Copy link
Copy Markdown

At the moment custom node authors have a variety of dependency installation mechanisms. Here are some examples from my list of custom nodes:

A simple start would be to warn users when additional packages are being installed that it might break their system. Another option that isn't too onerous would be to use the pip constraints feature to preserve the integrity of the main package.

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 14, 2026

A simple start would be to warn users when additional packages are being installed that it might break their system. Another option that isn't too onerous would be to use the pip constraints feature to preserve the integrity of the main package.

AFAIK Invoke doesn't install node requirements automatically; that's left to the user to do manually and should be documented by the node author in some fashion. @Pfannkuchensack That's still the case, right?

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

Automatic pip install of requirements.txt dependencies
This pr will install the dependencies if there is any.

But i think we should make that a option and not Automatic. A Checkbox with default to dont install Automatic

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 14, 2026

@Pfannkuchensack Before I review, does this support updating nodes (via git pull or other mechanism)? How do to that should be documented somewhere, if not. Or if it's trivial to add in, it would be nice to see a "Check and Update" to the left of "Uninstall".

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

Update is not build in right now. but could be easy added.

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 14, 2026

Automatic pip install of requirements.txt dependencies
This pr will install the dependencies if there is any.

But i think we should make that a option and not Automatic. A Checkbox with default to dont install Automatic

Yes, and also maybe a dialog that tells you what's going to be installed with constraints of not downgrading already installed versions or causing conflicts.

In truth, I think even this feels wrong and overly complex, and it will become a maintenance headache. I actually have a node package that has all sorts of requirements but pins the version of Invoke!

@Pfannkuchensack
Copy link
Copy Markdown
Collaborator Author

We need to maintain the venv that is right. But i think if a node has a bad/wrong dependencie that is a node author thing.

@iwr-redmond
Copy link
Copy Markdown

iwr-redmond commented Apr 14, 2026

I agree that pushing as much responsibility off to node authors as possible is the right way to do things. Enforcing sensible limits that protect the venv will make third parties more likely to follow the best practices that are needed, since painting outside the lines will prevent their extensions from being installable from within the UI.

AFAIK Invoke doesn't install node requirements automatically

In this PR, there is an installer function here.

@iwr-redmond
Copy link
Copy Markdown

iwr-redmond commented Apr 14, 2026

Some other feedback:

  • You may wish to consider adding Dulwich as a dependency so that users (especially on Windows) do not need to have git installed for custom node installation to work
  • Since most Invoke users access the application through the Launcher, shouldn't the extension installer use uv pip after setting the VIRTUAL_ENV environment variable to $INVOKEAI_ROOT/.venv?

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 14, 2026

We need to maintain the venv that is right. But i think if a node has a bad/wrong dependencie that is a node author thing.

I agree to both, but we cannot have nodes fighting each other for dependencies. Just as models are validated before installing, nodes should be flagged as incompatible (or invalid) if they introduce conflicting dependencies. That does mean that if node A requires foo==1.8 and node B requires foo<=1.6, one or the other can be installed but not both. I'm fairly sure you can get this information out of uv pip. But I definitely don't think it's appropriate to let people install node requirements if we haven't fleshed this out thoroughly.

So, my recommendation prior to reviewing this PR is to remove the automatic requirements.txt or pyproject.toml installation for version 1 and to have a detection with a toast that informs the user that they will have to perform an additional requirements installation using that node's documentation as the guide.

@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 14, 2026

(Plus, add in the automatic search for updates feature just to make everything complex yet again.)

Auto-running pip on requirements.txt could pull incompatible
packages into the running InvokeAI env and break the app. The
installer now detects requirements.txt or pyproject.toml,
returns requires_dependencies + dependency_file, and the UI
shows a persistent warning toast pointing the user to the
node pack's documentation.
Copy link
Copy Markdown
Collaborator

@JPPhoto JPPhoto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had time to do a quick review (more to come) and I found a number of authentication and security issues that aren't in line with the rest of the code after the recent introduction of more extensive multi-user support.

invokeai/app/api/routers/custom_nodes.py:133 exposes install, uninstall, and reload routes without any auth dependency. In this repo, admin-only routes normally require AdminUserOrDefault from invokeai/app/api/auth_dependencies.py:138, but these handlers take no current_user at all. That means a caller can hit the custom-nodes install endpoint, make the server run git clone, and then execute arbitrary Python through exec_module(). This is effectively unauthenticated remote code execution.

invokeai/app/api/routers/custom_nodes.py:392 imports workflows with workflow_records.create(workflow=workflow) and does not set ownership or sharing flags. The storage default in invokeai/app/services/workflow_records/workflow_records_base.py:26 is user_id="system" and is_public=False. But workflow listing in invokeai/app/api/routers/workflows.py:153 filters user workflows to the current user unless they are shared. In multiuser mode, imported workflows will therefore not appear in the installing user's library.

The new UI still ships direct English strings instead of routing everything through en.json. invokeai/frontend/web/src/features/customNodes/CustomNodesList.tsx:47 hardcodes node and nodes, and invokeai/frontend/web/src/features/customNodes/CustomNodesInstallLog.tsx:96 renders raw status values like installing and completed directly. Those should use translation keys from invokeai/frontend/web/public/locales/en.json.

…ize UI

- Gate install/uninstall/reload routes on AdminUserOrDefault so they respect multiuser auth
- Import pack workflows under the installing admin with is_public=True so all users see them
- Replace hardcoded English strings in CustomNodesList and CustomNodesInstallLog with translations
- Reuse existing common/queue keys for clear/status, drop duplicates in en.json
…r_user_id

Pass owner_user_id="admin" in all call sites and assert that user_id and
is_public=True are forwarded to workflow_records.create().
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 16, 2026

Edited messages from my LLM:

Findings

  • High: invokeai/app/api/routers/custom_nodes.py:389, invokeai/app/api/routers/custom_nodes.py:416, and invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx:124 still make uninstall data-destructive for unrelated workflows. The import path tags workflows with node-pack:<pack_name>, but uninstall deletes every user workflow whose tags field matches that string, with no provenance check. Workflow tags are user-editable in the existing workflow editor, so an admin uninstall can delete a user-authored workflow that merely retained or reused the same tag. The evidence chain is: imported workflows are marked only by a tag -> tags are editable user data -> uninstall queries by tag alone -> every matched workflow is deleted. To expose this issue, add a test that creates a non-imported workflow with the same node-pack:<pack_name> tag and verifies uninstall preserves it.

  • Medium: invokeai/app/api/routers/custom_nodes.py:113 and invokeai/app/api/routers/custom_nodes.py:128 still expose absolute server filesystem paths through an unauthenticated listing endpoint, and the new UI renders those paths in invokeai/frontend/web/src/features/customNodes/CustomNodesList.tsx:53 and derives the parent directory in invokeai/frontend/web/src/features/customNodes/ScanNodesForm.tsx:10. The earlier auth fix only covered install, uninstall, and reload; GET /v2/custom_nodes/ still has no auth dependency. The evidence chain is: list route has no auth guard -> _get_installed_packs() serializes path=str(d) -> frontend displays that path. In a hosted or multiuser deployment, any caller who can hit the endpoint can enumerate server-local directory structure. To expose this issue, add an API test that verifies unauthenticated or non-admin callers cannot retrieve absolute custom-node paths.

  • Low: invokeai/frontend/web/src/features/customNodes/ScanNodesForm.tsx:10-12 is still path-separator brittle and breaks the "Nodes directory" display on Windows. It strips the parent directory with lastIndexOf('/'), but Windows paths from the backend use backslashes. In that case lastIndexOf('/') is -1, so the computed directory becomes empty and the UI hides the value entirely. The evidence chain is: backend returns platform-native filesystem path -> UI assumes / separator -> Windows path yields no visible directory. To expose this issue, add a small logic test around parent-directory extraction that covers both POSIX and Windows-style paths.

I found a few weaker concerns, but I would not promote them to findings yet:

  • invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx:40 exposes the Custom Nodes tab to all users even though the backend now treats install, uninstall, and reload as admin-only. That is a UX mismatch, but not a proven defect by itself because I did not verify the intended product policy for read-only access to that screen.

  • invokeai/app/api/routers/custom_nodes.py:140 names the injected admin dependency current_admin but never reads it. That is stylistically odd, not a bug.

  • invokeai/frontend/web/src/features/customNodes/InstallFromGitForm.tsx:55 collapses all thrown install failures into the generic customNodes.installError log entry. That can hide server-provided details in the client log, but the server response path still carries a message on non-throwing failures, so I would treat this as a product/design question unless the API actually throws rich error payloads that the UI drops.

…isting

- Record imported workflow IDs in .invokeai_pack_manifest.json inside the pack
  directory; uninstall reads the manifest before rmtree and deletes only those
  IDs, so user-authored workflows sharing the pack tag are preserved
- Gate GET /v2/custom_nodes/ with AdminUserOrDefault to match install/uninstall
  /reload and prevent unauthenticated disclosure of absolute node pack paths
- Extract getParentDirectory() helper that handles both POSIX and Windows
  separators so the nodes-directory label renders on all platforms
- Add regression tests for manifest roundtrip, colliding-tag preservation, and
  parent-directory extraction across separator styles
@Pfannkuchensack Pfannkuchensack force-pushed the feature/custom-node-manager branch from 3357c79 to ef316d3 Compare April 16, 2026 20:39
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 16, 2026

Weaker Concerns

  • invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx:40, invokeai/frontend/web/src/features/customNodes/CustomNodesList.tsx:41, invokeai/frontend/web/src/features/customNodes/CustomNodesList.tsx:77, and invokeai/frontend/web/src/features/customNodes/InstallFromGitForm.tsx:89 still expose the Custom Nodes screen and its install, uninstall, and reload controls without any frontend admin gate. The backend now correctly protects those routes with AdminUserOrDefault in invokeai/app/api/routers/custom_nodes.py:133, invokeai/app/api/routers/custom_nodes.py:148, invokeai/app/api/routers/custom_nodes.py:259, and invokeai/app/api/routers/custom_nodes.py:324, so the likely runtime behavior for a non-admin is "UI advertises actions, server rejects them." That is a credible UX and product-consistency issue, To expose this issue, add a UI-logic test that verifies non-admin users in multiuser mode do not see admin-only Custom Nodes navigation or action controls, or else document and test the intended read-only behavior explicitly.

  • invokeai/tests/app/routers/test_custom_nodes.py remains helper-focused. The added tests do cover the manifest-based uninstall behavior well, including the prior tag-collision risk, but they still do not exercise route-level auth behavior for list_custom_node_packs, install_custom_node_pack, uninstall_custom_node_pack, or reload_custom_nodes. That means the recent auth hardening is proven by code inspection rather than by API tests, which lowers regression resistance. I am keeping this as a weaker concern rather than a finding because the route signatures themselves are clear and consistent. To expose this issue, add API tests that verify non-admin callers are rejected for list, install, uninstall, and reload, and that an admin caller succeeds.

Pfannkuchensack and others added 3 commits April 17, 2026 00:21
Add useIsCustomNodesEnabled hook (mirrors useIsModelManagerEnabled) and
conditionally render the tab in VerticalNavBar. Backend routes already
reject non-admin callers; this prevents the UI from advertising controls
that would 403.
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 17, 2026

Findings

  • Medium: non-admin users can still land on the Custom Nodes screen through persisted UI state even after the navbar gate was added. invokeai/frontend/web/src/features/ui/store/uiTypes.ts:4 still allows activeTab: 'customNodes', that state is persisted through invokeai/frontend/web/src/features/ui/store/uiSlice.ts:98, and invokeai/frontend/web/src/features/ui/components/AppContent.tsx:40 renders CustomNodesTabAutoLayout whenever the active tab is customNodes with no permission check. The new gate only hides the nav button in invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx:42; it does not sanitize persisted state or guard the tab content itself. Once that tab renders, invokeai/frontend/web/src/features/customNodes/CustomNodesList.tsx:64 immediately calls the admin-only list endpoint, and the install pane renders admin-only actions as well. The evidence chain is: persisted activeTab can still be customNodes -> tab content is rendered unconditionally -> mounted components call admin-only endpoints -> a non-admin can still hit an unauthorized management screen and trigger 403-backed requests. To expose this issue, add a test that rehydrates UI state with activeTab='customNodes' for a non-admin user in multiuser mode and verifies the app redirects to an allowed tab or suppresses the Custom Nodes content entirely.

Weaker Concerns

  • invokeai/tests/app/routers/test_custom_nodes.py has grown from 21 to 27 passing tests and now covers the manifest-based uninstall fix well, but it still does not include route-level auth tests for list_custom_node_packs, install_custom_node_pack, uninstall_custom_node_pack, or reload_custom_nodes in invokeai/app/api/routers/custom_nodes.py. The auth hardening looks correct by inspection, but there is still no API test that would catch a future removal of AdminUserOrDefault. To expose this issue, add tests that verify non-admin callers are rejected and admin callers succeed for all four endpoints.

  • invokeai/frontend/web/src/features/customNodes/useIsCustomNodesEnabled.ts:16 is the new permission hook, but I do not see a corresponding frontend test for the multiuser/admin matrix. Given that the PR now relies on that hook for the navbar gate in invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx:25, the absence of a logic-level test leaves the frontend permission behavior easier to regress than it should be. To expose this issue, add a logic test that covers single-user mode, multiuser admin, and multiuser non-admin inputs for useIsCustomNodesEnabled.

…add auth regression tests

- Suppress CustomNodesTabAutoLayout render and redirect to generate via
  navigationApi.switchToTab when a non-admin user lands on a persisted
  customNodes tab
- Add TestCustomNodesAuthorization with 10 route-level tests verifying
  unauthenticated (401), non-admin (403), and admin (200) for list,
  install, uninstall, and reload endpoints
- Add decision-matrix test for useIsCustomNodesEnabled covering
  single-user, multiuser admin, multiuser non-admin, and unloaded user
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 17, 2026

Two more things, minor:

  • invokeai/frontend/web/src/features/customNodes/useIsCustomNodesEnabled.test.ts:9 does not test the actual hook; it tests a local reimplementation of the decision table. That still gives some regression value, but it can drift from invokeai/frontend/web/src/features/customNodes/useIsCustomNodesEnabled.ts:16 if the hook later adds behavior around loading state, selector wiring, or setup-status handling. To expose this issue, add a logic-level test around the real exported implementation or refactor the decision into a shared pure helper that both the hook and the test import.

  • tests/app/routers/test_multiuser_authorization.py:1873 and tests/app/routers/test_multiuser_authorization.py:1877 only verify admin success for list and reload. The new tests do a good job covering unauthenticated and non-admin rejection for all four custom-node routes, but they still do not prove the happy path for admin install and uninstall. I am keeping this below finding level because the route auth boundary itself is now well covered and the missing positive cases are more about regression completeness than a demonstrated defect. To expose this issue, add tests that mock filesystem and subprocess behavior so admin callers can successfully exercise install and uninstall as well.

Extract getIsCustomNodesEnabled so test imports the real logic
instead of a local reimplementation. Add install/uninstall
admin-success tests with mocked filesystem/subprocess.
@JPPhoto
Copy link
Copy Markdown
Collaborator

JPPhoto commented Apr 18, 2026

Findings

  • Medium: invokeai/frontend/web/src/features/customNodes/useIsCustomNodesEnabled.ts:31 introduces a startup-time false negative that can incorrectly kick allowed single-user users out of the Custom Nodes tab. The hook treats missing setupStatus as if multiuser_enabled were true, and invokeai/frontend/web/src/features/ui/components/AppContent.tsx:38 immediately redirects any customNodes active tab to generate when the hook returns false. The evidence chain is: initial RTK Query state can be "not loaded yet" -> the hook defaults that state to multiuser mode -> isCustomNodesEnabled becomes false when user?.is_admin is still undefined -> persisted activeTab='customNodes' is redirected away before setup status resolves -> a legitimate single-user session loses its restored tab and never returns to Custom Nodes automatically. The new test file invokeai/frontend/web/src/features/customNodes/useIsCustomNodesEnabled.test.ts:20 covers only the pure helper's explicit inputs and does not cover the hook's "setup status not loaded yet" behavior that triggers this redirect. To expose this issue, add a logic-level test that rehydrates activeTab='customNodes' with setup status initially unresolved in single-user mode and verifies the app does not redirect away once that unresolved state is still compatible with allowed access.

Weaker Concerns

  • invokeai/frontend/web/src/features/customNodes/ScanNodesForm.tsx:12 derives the displayed "Nodes directory" from data?.node_packs?.[0]?.path, so the scan-folder pane can only show the directory if at least one node pack is already installed. But the copy in invokeai/frontend/web/public/locales/en.json:3326 says users can place node packs in the nodes directory and reload, which is most useful precisely when the list is empty. The evidence chain is: scan pane only knows the directory through the first installed pack -> empty install state means no first pack -> nodesDir stays null -> the UI hides the directory label on the first-use path where the user most needs it. I am keeping this below finding level because it is a UX gap, not a correctness or security defect. To expose this issue, add a logic or API-backed test that covers the zero-installed-packs state and verifies the scan-folder pane can still surface the configured nodes directory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api docs PRs that change docs frontend PRs that change frontend files invocations PRs that change invocations python PRs that change python files python-tests PRs that change python tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants