chore: import upstream snapshot with attribution
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:22:06 +08:00
commit cddb07a176
3370 changed files with 685519 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg width="75" height="32" viewBox="0 0 75 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="75" height="32" rx="2" fill="#E6FD13"/>
<g clip-path="url(#clip0_4_5)">
<path d="M18.6457 11.8788H23.3021V8.72727H8.75662V11.8788H13.413L18.6457 20.1212H23.3021V23.2727H8.75662V20.1212H13.413" stroke="black" stroke-width="1.21212"/>
</g>
<path d="M30.9033 12.3906H35.0928V13.3623H33.5742V18.5332H35.0928V19.5H30.9033V18.5332H32.3877V13.3623H30.9033V12.3906ZM36.7627 19.5V14.2168H37.8174L37.8906 14.9688C37.9622 14.8678 38.0404 14.7751 38.125 14.6904C38.2129 14.6025 38.3057 14.5244 38.4033 14.4561C38.5596 14.3486 38.7305 14.2656 38.916 14.207C39.1016 14.1484 39.2969 14.1191 39.502 14.1191C39.7721 14.1191 40.0195 14.1582 40.2441 14.2363C40.4688 14.3145 40.6608 14.4382 40.8203 14.6074C40.9798 14.7767 41.1035 14.9932 41.1914 15.2568C41.2793 15.5173 41.3232 15.833 41.3232 16.2041V19.5H40.1562V16.2236C40.1562 16.0055 40.1318 15.8232 40.083 15.6768C40.0342 15.5303 39.9626 15.4131 39.8682 15.3252C39.7738 15.2373 39.6598 15.1755 39.5264 15.1396C39.3929 15.1006 39.2399 15.0811 39.0674 15.0811C38.9242 15.0811 38.7907 15.1006 38.667 15.1396C38.5433 15.1755 38.431 15.2275 38.3301 15.2959C38.252 15.348 38.1787 15.4115 38.1104 15.4863C38.042 15.5612 37.9818 15.6426 37.9297 15.7305V19.5H36.7627ZM44.4971 19.5L42.417 14.2168H43.6279L44.9365 18.0498L45.0146 18.4014L45.0928 18.0498L46.3867 14.2168H47.5977L45.5273 19.5H44.4971ZM48.5596 16.8096C48.5596 16.4255 48.6149 16.0706 48.7256 15.7451C48.8363 15.4163 48.9974 15.1315 49.209 14.8906C49.4173 14.6497 49.6729 14.4609 49.9756 14.3242C50.2783 14.1875 50.6234 14.1191 51.0107 14.1191C51.3981 14.1191 51.7432 14.1875 52.0459 14.3242C52.3519 14.4609 52.6107 14.6497 52.8223 14.8906C53.0306 15.1315 53.1901 15.4163 53.3008 15.7451C53.4115 16.0706 53.4668 16.4255 53.4668 16.8096V16.9121C53.4668 17.2995 53.4115 17.6559 53.3008 17.9814C53.1901 18.307 53.0306 18.5902 52.8223 18.8311C52.6139 19.0719 52.3568 19.2607 52.0508 19.3975C51.748 19.5342 51.4046 19.6025 51.0205 19.6025C50.6331 19.6025 50.2865 19.5342 49.9805 19.3975C49.6745 19.2607 49.4173 19.0719 49.209 18.8311C48.9974 18.5902 48.8363 18.307 48.7256 17.9814C48.6149 17.6559 48.5596 17.2995 48.5596 16.9121V16.8096ZM49.7266 16.9121C49.7266 17.1497 49.751 17.3743 49.7998 17.5859C49.8519 17.7975 49.9316 17.9831 50.0391 18.1426C50.1432 18.3021 50.2767 18.429 50.4395 18.5234C50.6022 18.6146 50.7959 18.6602 51.0205 18.6602C51.2386 18.6602 51.429 18.6146 51.5918 18.5234C51.7546 18.429 51.888 18.3021 51.9922 18.1426C52.0964 17.9831 52.1729 17.7975 52.2217 17.5859C52.2738 17.3743 52.2998 17.1497 52.2998 16.9121V16.8096C52.2998 16.5785 52.2738 16.3571 52.2217 16.1455C52.1696 15.9339 52.0931 15.7484 51.9922 15.5889C51.8848 15.4294 51.7497 15.3024 51.5869 15.208C51.4274 15.1136 51.2354 15.0664 51.0107 15.0664C50.7894 15.0664 50.5973 15.1136 50.4346 15.208C50.2751 15.3024 50.1432 15.4294 50.0391 15.5889C49.9316 15.7484 49.8519 15.9339 49.7998 16.1455C49.751 16.3571 49.7266 16.5785 49.7266 16.8096V16.9121ZM56.5967 17.2002L55.9619 17.8008V19.5H54.79V12H55.9619V16.4141L56.4453 15.877L58.0225 14.2168H59.4287L57.373 16.4189L59.7266 19.5H58.2812L56.5967 17.2002ZM63.252 19.5977C62.8613 19.5977 62.5033 19.5326 62.1777 19.4023C61.8555 19.2721 61.5788 19.0915 61.3477 18.8604C61.1165 18.6325 60.9375 18.3639 60.8105 18.0547C60.6868 17.7422 60.625 17.4053 60.625 17.0439V16.8438C60.625 16.4303 60.6901 16.056 60.8203 15.7207C60.9505 15.3854 61.1296 15.099 61.3574 14.8613C61.5853 14.6237 61.849 14.4414 62.1484 14.3145C62.4512 14.1842 62.7734 14.1191 63.1152 14.1191C63.4961 14.1191 63.833 14.1842 64.126 14.3145C64.4189 14.4414 64.6647 14.6188 64.8633 14.8467C65.0618 15.0778 65.2116 15.3529 65.3125 15.6719C65.4134 15.9909 65.4639 16.3392 65.4639 16.7168V17.2197H61.8018V17.2441C61.8376 17.4753 61.8929 17.6689 61.9678 17.8252C62.0426 17.9814 62.1452 18.1214 62.2754 18.2451C62.4056 18.3753 62.5586 18.4762 62.7344 18.5479C62.9134 18.6195 63.1087 18.6553 63.3203 18.6553C63.61 18.6553 63.8802 18.5999 64.1309 18.4893C64.3815 18.3753 64.5882 18.2142 64.751 18.0059L65.376 18.6113C65.2002 18.8652 64.93 19.0931 64.5654 19.2949C64.2041 19.4967 63.7663 19.5977 63.252 19.5977ZM63.1104 15.0664C62.9443 15.0664 62.7897 15.0973 62.6465 15.1592C62.5065 15.2178 62.3812 15.3024 62.2705 15.4131C62.1598 15.527 62.0671 15.6637 61.9922 15.8232C61.9173 15.9827 61.862 16.1634 61.8262 16.3652H64.3115V16.2871C64.3115 16.1341 64.2839 15.9827 64.2285 15.833C64.1732 15.68 64.0951 15.5465 63.9941 15.4326C63.8965 15.3219 63.7728 15.234 63.623 15.1689C63.4766 15.1006 63.3057 15.0664 63.1104 15.0664Z" fill="black"/>
<defs>
<clipPath id="clip0_4_5">
<rect width="16" height="16" fill="white" transform="translate(8 8)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="66" height="66" viewBox="0 0 66 66" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M43.9137 16H63.1211V3H3.12109V16H22.3285L43.9137 50H63.1211V63H3.12109V50H22.3285" stroke="white" stroke-width="5"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B

+79
View File
@@ -0,0 +1,79 @@
import type { StarlightUserConfig } from '@astrojs/starlight/types';
type HeadConfig = NonNullable<StarlightUserConfig['head']>;
type CreateHeadConfigParams = {
base: string;
enableAnalytics: boolean;
isGhPages: boolean;
site: string;
};
const plausibleScriptUrl =
'https://plausible.tracking.events/js/pa-BHcumuOemKz4XIQeWkTn4.js';
const plausibleInitScript =
'window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};plausible.init()';
function createHeadConfig({
base,
enableAnalytics,
isGhPages,
site,
}: CreateHeadConfigParams): HeadConfig {
const coverImageUrl = new URL(`${base}/coverimage.png`, site).toString();
return [
{
tag: 'meta',
attrs: {
property: 'og:image',
content: coverImageUrl,
},
},
{
tag: 'meta',
attrs: {
property: 'og:image:width',
content: '1200',
},
},
{
tag: 'meta',
attrs: {
property: 'og:image:height',
content: '630',
},
},
{
tag: 'meta',
attrs: {
name: 'twitter:card',
content: 'summary_large_image',
},
},
{
tag: 'meta',
attrs: {
name: 'twitter:image',
content: coverImageUrl,
},
},
...(enableAnalytics && !isGhPages
? ([
{
tag: 'script',
attrs: {
async: true,
src: plausibleScriptUrl,
},
},
{
tag: 'script',
content: plausibleInitScript,
},
] satisfies HeadConfig)
: []),
] satisfies HeadConfig;
}
export { createHeadConfig };
+4
View File
@@ -0,0 +1,4 @@
export * from './head';
export * from './redirects';
export * from './sidebar';
export * from './social';
+55
View File
@@ -0,0 +1,55 @@
import type { AstroConfig } from 'astro';
type RedirectsConfig = AstroConfig['redirects'];
const redirects: RedirectsConfig = {
'/CODE_OF_CONDUCT': '/contributing/code-of-conduct',
'/RELEASE': '/development/process/release-process',
'/installation': '/start-here/installation',
'/installation/docker': '/configuration/docker',
'/installation/manual': '/start-here/manual',
'/installation/models': '/concepts/models',
'/installation/patchmatch': '/configuration/patchmatch',
'/installation/quick_start': '/start-here/installation',
'/installation/requirements': '/start-here/system-requirements',
'/configuration': '/configuration/invokeai-yaml',
'/features/low-vram/': '/configuration/low-vram-mode/',
'/features/lasso-tool': '/features/canvas/lasso-tool',
'/features/shapes-tool': '/features/canvas/shapes-tool',
'/faq': '/troubleshooting/faq',
'/help/SAMPLER_CONVERGENCE': '/concepts/parameters',
'/help/diffusion': '/concepts/diffusion',
'/help/gettingStartedWithAI': '/concepts/image-generation',
'/nodes/NODES': '/features/workflows/editor-interface',
'/nodes/NODES_MIGRATION_V3_V4': '/development/guides/api-development',
'/nodes/comfyToInvoke': '/features/workflows/comfyui-migration',
'/nodes/communityNodes': '/features/workflows/community-nodes',
'/nodes/contributingNodes': '/development/guides/creating-nodes',
'/nodes/detailedNodes/faceTools': '/features/workflows/face-tools',
'/nodes/invocation-api': '/development/guides/api-development',
'/contributing/ARCHITECTURE': '/development/architecture/overview',
'/contributing/DOWNLOAD_QUEUE': '/development/architecture/model-manager',
'/contributing/HOTKEYS': '/features/hotkeys',
'/contributing/INVOCATIONS': '/development/architecture/invocations',
'/contributing/LOCAL_DEVELOPMENT': '/development/setup/dev-environment',
'/contributing/MODEL_MANAGER': '/development/architecture/model-manager',
'/contributing/NEW_MODEL_INTEGRATION': '/development/guides/models',
'/contributing/PR-MERGE-POLICY': '/development/process/pr-merge-policy',
'/contributing/TESTS': '/development/guides/tests',
'/contributing/contribution_guides/development': '/development',
'/contributing/contribution_guides/newContributorChecklist':
'/contributing/new-contributor-guide',
'/contributing/dev-environment': '/development/setup/dev-environment',
'/contributing/frontend': '/development/front-end',
'/contributing/frontend/state-management':
'/development/front-end/state-management',
'/contributing/frontend/workflows': '/development/front-end/workflows',
};
function createRedirects(base: string): RedirectsConfig {
return Object.fromEntries(
Object.entries(redirects).map(([from, to]) => [from, base + to]),
);
}
export { createRedirects };
+80
View File
@@ -0,0 +1,80 @@
import type { StarlightUserConfig } from '@astrojs/starlight/types';
import { makeChangelogsSidebarLinks } from 'starlight-changelogs';
type SidebarConfig = StarlightUserConfig['sidebar'];
const sidebar: SidebarConfig = [
{
label: 'Start Here',
items: [
{
autogenerate: { directory: 'start-here' },
},
],
},
{
label: 'Configuration',
items: [
{
autogenerate: { directory: 'configuration' },
},
],
},
{
label: 'Concepts',
items: [
{
autogenerate: { directory: 'concepts' },
},
],
},
{
label: 'Features',
items: [
{
autogenerate: { directory: 'features' },
},
],
},
{
label: 'Development',
items: [
{
autogenerate: { directory: 'development', collapsed: true },
},
],
collapsed: true,
},
{
label: 'Contributing',
items: [
{
autogenerate: { directory: 'contributing' },
},
],
collapsed: true,
},
{
label: 'Troubleshooting & Help',
items: [
{
autogenerate: { directory: 'troubleshooting' },
},
],
collapsed: true,
},
{
label: 'Releases',
collapsed: true,
items: [
...makeChangelogsSidebarLinks([
{
type: 'recent',
base: 'releases',
},
]),
],
},
];
export { sidebar as sidebarConfig };
+23
View File
@@ -0,0 +1,23 @@
import type { StarlightUserConfig } from '@astrojs/starlight/types';
type SocialConfig = StarlightUserConfig['social'];
const social: SocialConfig = [
{
icon: 'github',
label: 'GitHub',
href: 'https://github.com/invoke-ai/InvokeAI',
},
{
icon: 'discord',
label: 'Discord',
href: 'https://discord.gg/ZmtBAhwWhy',
},
{
icon: 'youtube',
label: 'YouTube',
href: 'https://www.youtube.com/@invokeai',
},
];
export { social as socialConfig };
+28
View File
@@ -0,0 +1,28 @@
import { defineCollection } from 'astro:content';
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
import { changelogsLoader } from 'starlight-changelogs/loader';
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),
changelogs: defineCollection({
loader: changelogsLoader([
{
title: "Releases",
provider: 'github',
base: 'releases',
owner: 'invoke-ai',
repo: 'InvokeAI',
pagefind: false,
// Authenticate GitHub API requests so the release changelog loader uses
// the 5000 req/hr authenticated rate limit instead of the 60 req/hr
// unauthenticated limit (shared per CI runner IP), which causes
// intermittent "403 - rate limit exceeded" build failures. The token is
// optional, so local builds without it fall back to unauthenticated.
token: process.env.GITHUB_TOKEN,
}
]),
})
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -0,0 +1,77 @@
---
title: Diffusion
lastUpdated: 2026-02-20
sidebar:
order: 5
---
import { Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
Taking the time to understand the diffusion process will help you to understand how to more effectively use InvokeAI.
## Image Space vs. Latent Space
There are two main ways Stable Diffusion works — with images, and latents.
<CardGrid>
<Card title="Image Space" icon="seti:image">
Represents images in pixel form that you look at. This is the final visual output you see.
</Card>
<Card title="Latent Space" icon="puzzle">
Represents compressed inputs. It's in latent space that Stable Diffusion processes images.
</Card>
</CardGrid>
:::note[What is a VAE?]
A **VAE (Variational Auto Encoder)** is responsible for compressing and encoding inputs into *latent space*, as well as decoding outputs back into *image space*.
:::
## Core Components
To fully understand the diffusion process, we need to understand a few more terms: **U-Net**, **CLIP**, and **conditioning**.
<CardGrid>
<Card title="U-Net" icon="setting">
A model trained on a large number of latent images with known amounts of random noise added. The U-Net can be given a slightly noisy image and it will predict the pattern of noise needed to subtract from the image in order to recover the original.
</Card>
<Card title="CLIP & Conditioning" icon="document">
**CLIP** is a model that tokenizes and encodes text into **conditioning**. This conditioning guides the model during the denoising steps to produce a new image.
</Card>
</CardGrid>
The U-Net and CLIP work together during the image generation process at each denoising step. The U-Net removes noise so that the result is similar to images in its training set, while CLIP guides the U-Net towards creating images that are most similar to your prompt.
## The Generation Process
<Tabs>
<TabItem label="Text-to-Image" icon="seti:default">
When you generate an image using text-to-image, multiple steps occur in latent space:
<Steps>
1. **Noise Generation:** Random noise is generated at the chosen height and width. The noise's characteristics are dictated by the seed. This noise tensor is passed into latent space. We'll call this *noise A*.
2. **Noise Prediction:** Using a model's U-Net, a noise predictor examines *noise A* and the words tokenized by CLIP from your prompt (conditioning). It generates its own noise tensor to predict what the final image might look like in latent space. We'll call this *noise B*.
3. **Subtraction:** *Noise B* is subtracted from *noise A* in an attempt to create a latent image consistent with the prompt. This step is repeated for the number of sampler steps chosen.
4. **Decoding:** The VAE decodes the final latent image from latent space into image space.
</Steps>
</TabItem>
<TabItem label="Image-to-Image" icon="seti:image">
Image-to-image is a similar process, with only the first step being different:
<Steps>
1. **Encoding & Adding Noise:** The input image is encoded from image space into latent space by the VAE. Noise is then added to the input latent image.
* **Denoising Strength** dictates how many noise steps are added, and the amount of noise added at each step.
* A strength of `0` means there are 0 steps and no noise added, resulting in an unchanged image.
* A strength of `1` results in the image being completely replaced with noise and a full set of denoising steps are performed.
2. **Noise Prediction:** Using a model's U-Net, a noise predictor examines the noisy latent image and the conditioning from your prompt. It generates its own noise tensor to predict the final image.
3. **Subtraction:** The predicted noise is subtracted from the current noise in an attempt to create a latent image consistent with the prompt. This step is repeated for the remaining sampler steps.
4. **Decoding:** The VAE decodes the final latent image from latent space into image space.
</Steps>
</TabItem>
</Tabs>
## Summary
<Card title="Putting it all together" icon="star">
- A **Model** provides the CLIP prompt tokenizer, the VAE, and a U-Net (where noise prediction occurs given a prompt and initial noise tensor).
- A **Noise Scheduler** (e.g. `DPM++ 2M Karras`) schedules the subtraction of noise from the latent image across the sampler steps chosen. Less noise is usually subtracted at higher sampler steps.
</Card>
@@ -0,0 +1,133 @@
---
title: Dynamic Prompting
lastUpdated: 2026-03-30
sidebar:
order: 4
---
import { Card, CardGrid, Steps, LinkCard } from '@astrojs/starlight/components';
Dynamic prompting expands a single prompt into many prompt variations. It is useful for brainstorming, prompt exploration, and batch testing without rewriting the same prompt by hand.
## Basic syntax
Put alternatives inside braces and separate them with `|`.
```text
a {red|green|blue} balloon
```
This can expand into:
```text
a red balloon
a green balloon
a blue balloon
```
You can use more than one dynamic group in the same prompt:
```text
a {red|green} {balloon|kite}
```
That creates a set of prompt combinations such as `a red balloon`, `a red kite`, `a green balloon`, and `a green kite`.
## Select more than one option with `$$`
Prefix a group with a number and `$$` to choose multiple distinct options from the same set.
```text
portrait, {2$$rim light|fog|rain|neon reflections}
```
Possible results include:
```text
portrait, rim light, fog
portrait, fog, rain
portrait, rim light, neon reflections
```
This is useful when you want controlled variety without writing every combination by hand.
## Random vs combinatorial expansion
<CardGrid>
<Card title="Combinatorial" icon="setting">
Walks the possible prompt combinations systematically until `Max Prompts` is reached.
</Card>
<Card title="Random" icon="star">
Samples prompt variations instead of enumerating every combination. A seed can make random expansion repeatable.
</Card>
</CardGrid>
InvokeAI supports both modes, but where you can choose them depends on the workflow.
- In the current linear UI, dynamic prompt preview is driven from the positive prompt and currently follows the standard combinatorial expansion path.
- In node and backend contexts, random and combinatorial generation are exposed more explicitly.
## Max Prompts
`Max Prompts` limits how many expanded prompts InvokeAI will generate.
This matters because combinations grow quickly. For example:
```text
a {red|green|blue} balloon in {morning mist|golden hour|rain}
```
Even this small prompt already has nine possible combinations.
:::tip[Start small]
Preview a handful of prompt variants first. Once the combinations look useful, increase `Max Prompts` for a larger batch.
:::
## Seed Behaviour
In the current UI, the `Seed Behaviour` setting controls how seeds are reused across expanded prompts.
<CardGrid>
<Card title="Seed per Iteration" icon="seti:image">
Uses one seed per iteration, so prompt variants in the same iteration share a seed. This is useful when you want to compare prompt wording more directly.
</Card>
<Card title="Seed per Image" icon="star">
Uses a different seed for every generated image. This is useful when you want the widest possible variety.
</Card>
</CardGrid>
## Using dynamic prompting in the linear UI
<Steps>
1. **Put dynamic prompt syntax in the positive prompt**
In the current linear UI, dynamic prompt expansion is driven from the positive prompt.
2. **Open the preview**
Use `Show Dynamic Prompts` or the prompts preview to inspect the expanded list before you generate.
3. **Set `Max Prompts`**
Keep the expansion under control before launching a large batch.
4. **Choose the right seed behavior**
Use `Seed per Iteration` for easier comparison, or `Seed per Image` for more variety.
5. **Generate a small batch first**
Sanity-check the combinations before scaling up.
</Steps>
:::note[Current linear UI behavior]
The linear UI currently exposes `Max Prompts`, preview, and seed behavior. It does not expose a separate random-versus-combinatorial mode switch in the main positive prompt flow.
:::
## Tips
- Keep each option group internally compatible.
- Be careful with multiple groups, because the number of combinations grows quickly.
- Review the expanded prompt list before launching a large batch.
- Use dynamic prompting for variation, not to avoid thinking through the base prompt.
- When one specific term needs more emphasis, use [Prompting Syntax](../prompt-syntax) instead of adding more dynamic groups.
@@ -0,0 +1,153 @@
---
title: Image Generation
lastUpdated: 2026-03-30
sidebar:
order: 1
---
import { Card, CardGrid, Steps, LinkCard } from '@astrojs/starlight/components';
:::tip[New to image generation with AI?]
You're in the right place! This is a high-level walkthrough of some of the concepts and terms you'll see as you start using Invoke. Please note, this is not an exhaustive guide and may be out of date due to the rapidly changing nature of the space.
:::
## Using InvokeAI
### Prompt Crafting
Prompts are the basis of using InvokeAI, providing the models directions on what to generate. As a general rule of thumb, the more detailed your prompt is, the better your result will be.
<Card title="Prompt Structuring Template" icon="pencil">
To get started, here's an easy template to use for structuring your prompts:
**Subject, Style, Quality, Aesthetic**
- **Subject:** What your image will be about. E.g. “a futuristic city with trains”, “penguins floating on icebergs”, “friends sharing beers”.
- **Style:** The style or medium in which your image will be in. E.g. “photograph”, “pencil sketch”, “oil paints”, or “pop art”, “cubism”, “abstract”.
- **Quality:** A particular aspect or trait that you would like to see emphasized in your image. E.g. "award-winning", "featured in relevant set of high quality works", "professionally acclaimed". Many people often use "masterpiece".
- **Aesthetics:** The visual impact and design of the artwork. This can be colors, mood, lighting, setting, etc.
</Card>
There are two prompt boxes: **Positive Prompt** & **Negative Prompt**.
- A **Positive Prompt** includes words you want the model to reference when creating an image.
- A **Negative Prompt** is for anything you want the model to eliminate when creating an image. It doesnt always interpret things exactly the way you would, but helps control the generation process. Always try to include a few terms - you can typically use lower quality image terms like “blurry” or “distorted” with good success.
**Some example prompts you can try on your own:**
- *A detailed oil painting of a tranquil forest at sunset with vibrant colors and soft, golden light filtering through the trees*
- *friends sharing beers in a busy city, realistic colored pencil sketch, twilight, masterpiece, bright, lively*
### Advanced Prompting
<CardGrid>
<LinkCard
title="Prompting Guide"
description="Learn how to structure prompts, use positive and negative prompts well, and iterate toward better results."
href="../prompting-guide"
/>
<LinkCard
title="Prompting Syntax"
description="Learn InvokeAI's advanced prompt weighting and composition syntax, including `+`, `-`, `.blend()`, and `.and()`."
href="../prompt-syntax"
/>
<LinkCard
title="Dynamic Prompting"
description="Expand one prompt into many prompt variations with curly-brace syntax."
href="../dynamic-prompting"
/>
</CardGrid>
### Generation Workflows
Invoke offers a number of different workflows for interacting with models to produce images. Each is extremely powerful on its own, but together provide you an unparalleled way of producing high quality creative outputs that align with your vision.
<CardGrid>
<Card title="Text to Image" icon="seti:default">
Focuses on the key workflow of using a prompt to generate a new image. It includes other features that help control the generation process as well.
</Card>
<Card title="Image to Image" icon="seti:image">
Provide an image as a reference (called the “initial image”), which provides more guidance around color and structure to the AI as it generates a new image.
</Card>
<Card title="Unified Canvas" icon="pencil">
An advanced AI-first image editing tool. Drag an image onto the canvas to regenerate elements, edit content or colors (**inpainting**), or extend the image with consistency and clarity (**outpainting**).
</Card>
</CardGrid>
### Improving Image Quality
<Steps>
1. **Fine-tuning your prompt:**
The more specific you are, the closer the image will turn out to what is in your head. Adding more details in the Positive or Negative Prompt can help add or remove parts of the image. You can also use advanced techniques like upweighting and downweighting to control the influence of specific words. Learn more in the [Prompting Guide](../prompting-guide) and [Prompting Syntax](../prompt-syntax).
:::tip
If you're seeing poor results, try adding the things you don't like about the image to your negative prompt. E.g. *distorted, low quality, unrealistic, etc.*
:::
2. **Explore different models:**
Other models can produce different results due to the data they've been trained on. Each model has specific language and settings it works best with; a model's documentation is your friend here. Play around with some and see what works best for you!
3. **Increasing Steps:**
The number of steps used controls how much time the model is given to produce an image, and depends on the "Scheduler" used. More steps tends to mean better results, but will take longer. We recommend at least 30 steps for most.
4. **Tweak and Iterate:**
Remember, it's best to change one thing at a time so you know what is working and what isn't. Sometimes you just need to try a new image, and other times using a new prompt might be the ticket.
*For testing, consider turning off the "random" Seed. Using the same seed with the same settings will produce the same image, which makes it the perfect way to learn exactly what your changes are doing.*
5. **Explore Advanced Settings:**
InvokeAI has a full suite of tools available to allow you complete control over your image creation process. Check out our [features docs](../../features/gallery) if you want to learn more.
</Steps>
## Terms & Concepts
:::note
If you're interested in learning more, check out [this presentation](https://docs.google.com/presentation/d/1IO78i8oEXFTZ5peuHHYkVF-Y3e2M6iM5tCnc-YBfcCM/edit?usp=sharing) from one of our maintainers (@lstein).
:::
### Stable Diffusion
Stable Diffusion is a deep learning, text-to-image model that is the foundation of the capabilities found in InvokeAI. Since the release of Stable Diffusion, there have been many subsequent models created based on Stable Diffusion that are designed to generate specific types of images.
### Prompts
Prompts provide the models directions on what to generate. As a general rule of thumb, the more detailed your prompt is, the better your result will be.
### Models
Models are the magic that power InvokeAI. These files represent the output of training a machine on understanding massive amounts of images - providing them with the capability to generate new images using just a text description of what you'd like to see.
Invoke offers a simple way to download several different models upon installation, but many more can be discovered online, including at [civitai.com](https://civitai.com). Each model can produce a unique style of output, based on the images it was trained on.
:::note
Models that contain "inpainting" in the name are designed for use with the inpainting feature of the Unified Canvas.
:::
### Schedulers & Steps
**Schedulers** guide the process of removing noise (de-noising) from data. They determine:
1. The number of steps to take to remove the noise.
2. Whether the steps are random (stochastic) or predictable (deterministic).
3. The specific method (algorithm) used for de-noising.
**Steps** represent the number of de-noising iterations each generation goes through. Schedulers can be intricate and there's often a balance to strike between how quickly they can de-noise data and how well they can do it. It's typically advised to experiment with different schedulers to see which one gives the best results.
### Additional Concepts
<CardGrid>
<Card title="Low-Rank Adaptations (LoRAs)">
LoRAs are like a smaller, more focused version of models, intended to focus on training a better understanding of how a specific character, style, or concept looks.
</Card>
<Card title="Textual Inversion Embeddings">
Like LoRAs, embeddings assist with more easily prompting for certain characters, styles, or concepts. They are trained to update the relationship between a specific word (known as the "trigger") and the intended output.
</Card>
<Card title="ControlNet">
ControlNets are neural network models that are able to extract key features from an existing image and use these features to guide the output of the image generation model.
</Card>
<Card title="VAE">
A Variational Auto-Encoder (VAE) is an encode/decode model that translates the "latents" image produced during the image generation process to the large pixel images that we see.
</Card>
</CardGrid>
+133
View File
@@ -0,0 +1,133 @@
---
title: Models
sidebar:
order: 8
---
## Checkpoint and Diffusers Models
The model checkpoint files (`*.ckpt`) are the Stable Diffusion "secret sauce". They are the product of training the AI on millions of captioned images gathered from multiple sources.
Originally there was only a single Stable Diffusion weights file, which many people named `model.ckpt`.
Today, there are thousands of models, fine tuned to excel at specific styles, genres, or themes.
:::tip[Model Formats]
We also have two more popular model formats, both created by [HuggingFace](https://huggingface.co/):
- `safetensors`: Single file, like `.ckpt` files. Prevents malware from lurking in a model.
- `diffusers`: Splits the model components into separate files, allowing very fast loading.
InvokeAI supports all three formats.
:::
## Starter Models
When you first start InvokeAI, you'll see a popup prompting you to install some starter models from the Model Manager. Click the `Starter Models` tab to see the list.
You'll find a collection of popular and high-quality models available for easy download.
Some models carry license terms that limit their use in commercial applications or on public servers. It's your responsibility to adhere to the license terms.
## Other Models
There are a few ways to install other models:
- **URL or Local Path**: Provide the path to a model on your computer, or a direct link to the model. Some sites require you to use an API token to download models, which you can [set up in the config file]. You can also paste a HuggingFace Repo ID here directly — it is detected and routed to the HuggingFace installer automatically.
- **HuggingFace**: Paste a HF Repo ID to install it. If there are multiple models in the repo, you'll get a list to choose from. Repo IDs look like this: `XpucT/Deliberate`. There is a copy button on each repo to copy the ID.
- **Scan Folder**: Scan a local folder for models. You can install all of the detected models in one click.
### Diffusers models in HF repo subfolders
HuggingFace repos can be structured in any way. Some model authors include multiple models within the same folder.
In this situation, you may need to provide some additional information to identify the model you want, by adding `:subfolder_name` to the repo ID.
:::note[Example]
Say you have a repo ID `monster-labs/control_v1p_sd15_qrcode_monster`, and the model you want is inside the `v2` subfolder.
Add `:v2` to the repo ID and use that when installing the model: `monster-labs/control_v1p_sd15_qrcode_monster:v2`
:::
[set up in the config file]: ../../configuration/invokeai-yaml
## Editing model metadata
Every model has an editable **Source URL** field alongside its name and description. Use it to record where a model came from — for example a Civitai or HuggingFace page — independent of how it was originally installed. The URL is editable from the model's **Edit** view and appears as a clickable link in the model header once set. Models without a URL simply hide the field.
This is purely metadata: the URL has no effect on loading and is not used to refresh or reinstall the model. It is mainly useful for going back to the model's documentation, license, or example prompts later.
## Bulk actions in the Model Manager
The Model Manager supports multi-selection for batch operations.
- **Select multiple models** by clicking with **Ctrl** (Windows / Linux) or **Cmd** (macOS) held, or by using the checkboxes on each row. A sticky header at the top shows the current selection count and is always visible while you scroll.
- Open the **Actions** dropdown for the selection. The available actions are:
- **Delete Models** — removes every selected model in a single confirmation step. Partial failures (e.g. permission issues) are reported per-model in the result toast.
- **Reidentify Models** — re-probes every selected model, updating fields that depend on the file contents (type, base, format, variant, etc.). This is the bulk version of the per-model reidentify action.
:::caution[Reidentify resets custom settings]
Reidentifying a model re-derives its configuration from the file on disk. Any custom settings you've adjusted on those models — default settings, descriptions, trigger phrases — may be overwritten. The confirmation modal warns you about this before running.
:::
Both actions handle partial failures: if some models succeed and others fail, the toast lists succeeded and failed counts and the list view updates immediately for the ones that worked.
## Finding orphaned models
If a model file is deleted or moved outside the Model Manager, its database entry sticks around. To find these orphaned entries:
1. Open the Model Manager.
2. Open the **type filter** dropdown and pick **Missing Files**.
3. The list now shows only models whose files are no longer present on disk. Each one also displays a **Missing Files** badge in its row.
Orphaned models are automatically excluded from selection dropdowns (main model, LoRA, VAE, etc.), so you cannot accidentally pick one for generation. Use the [bulk delete action](#bulk-actions-in-the-model-manager) to clean them out in one step.
## Synchronizing orphaned model directories
The **Missing Files** filter finds database records whose files are gone. InvokeAI also has a separate sync workflow for the opposite situation: model directories that still exist on disk but are not referenced in the database.
This can happen after a failed import, a manual database edit, or deleting a model record while leaving files behind. The sync workflow scans the models directory for top-level folders containing model files with common model extensions, including `.safetensors`, `.ckpt`, `.pt`, `.pth`, `.bin`, `.onnx`, and `.gguf`.
To review these directories:
1. In multi-user mode, sign in as an administrator. In single-user mode, the Model Manager controls are available by default.
2. Open the Model Manager.
3. Click **Sync Models** to scan for orphaned model directories.
4. Review each reported relative directory path, contained model files, and total size before deleting anything.
:::caution[Deletion removes directories]
Deleting an orphaned model directory removes the entire reported directory from disk. The server deletes it directly with recursive directory deletion, so make sure the directory contains only files you intend to remove.
:::
Only administrators can use this workflow in multi-user mode. The underlying API is `/api/v2/models/sync/orphaned`; API results also include the absolute path for each reported directory.
## Exporting and Importing Model Settings
Each installed model has an **Export Settings** and **Import Settings** action in the Model Manager. Use these to back up a model's configuration, move it to another install, or share a curated setup with someone else.
### What gets exported
The exported `.json` file captures the configuration you have set on the model, not the model weights themselves:
- `default_settings` — steps, CFG / guidance, scheduler, dimensions, FP8 storage toggle, VAE precision, etc.
- `trigger_phrases` — for LoRAs and similar.
- `cpu_only` — for encoder-type models.
- `name`, `description`, `source_url` — the model's identifying metadata.
- `cover_image` — the model's thumbnail, embedded as a base64 data URL.
Fields you have not set are omitted from the file. The format is forward and backward compatible: older clients ignore newer fields, and a file produced by a newer version still imports cleanly into an older one (it just skips the fields it does not understand).
### Importing
Importing applies the JSON to the currently selected model:
- `default_settings`, `trigger_phrases`, `cpu_only`, `name`, `description`, and `source_url` are applied via the normal model update path. Any field that the target model type does not support (e.g. `cpu_only` on a model that has no such setting) is listed in a "skipped" toast — everything else still applies.
- `cover_image` is uploaded and set as the model's thumbnail.
Imports are validated before they run. The file is rejected if `source_url` is not an `http(s)://` URL or if `cover_image` is not a valid image data URL — so a malformed or hand-edited file cannot quietly poison a model's configuration.
### Typical workflows
- **Back up a model you've spent time tuning** so you can restore its settings after a reinstall, or roll back after experimenting.
- **Copy settings between two installs of the same model** — e.g. between a desktop and a workstation.
- **Share a curated setup** (name, description, thumbnail, default steps / CFG / scheduler, trigger phrases) for a model you have configured well.
@@ -0,0 +1,29 @@
---
title: Nodes and Workflows
sidebar:
order: 7
---
import { Card, CardGrid } from '@astrojs/starlight/components';
## What are Nodes?
A **Node** is simply a single operation that takes in inputs and returns outputs. Multiple nodes can be linked together to create more complex functionality. All InvokeAI features are added through nodes.
With nodes, you can easily extend the image generation capabilities of InvokeAI and build workflows that suit your specific needs.
### Anatomy of a Node
Individual nodes are made up of the following:
<CardGrid>
<Card title="Inputs" icon="left-arrow">
Edge points on the **left side** of the node window where you connect outputs from other nodes.
</Card>
<Card title="Outputs" icon="right-arrow">
Edge points on the **right side** of the node window where you connect to inputs on other nodes.
</Card>
<Card title="Options" icon="setting">
Various options which are either manually configured, or overridden by connecting an output from another node to the input.
</Card>
</CardGrid>
@@ -0,0 +1,143 @@
---
title: Generation Parameters
lastUpdated: 2026-02-20
sidebar:
order: 6
---
import { Card, CardGrid, Steps } from '@astrojs/starlight/components';
# Sampler Convergence
As features keep increasing, making the right choices for your needs can become increasingly difficult. What sampler to use? And for how many steps? Do you change the CFG value? Do you use prompt weighting? Do you allow variations?
Even once you have a result, do you blend it with other images? Pass it through `img2img`? With what strength? Do you use inpainting to correct small details? Outpainting to extend cropped sections?
The purpose of this series of documents is to help you better understand these tools, so you can make the best out of them. Feel free to contribute with your own findings!
In this document, we will talk about **sampler convergence**.
<Card title="TL;DR" icon="rocket">
Looking for a short version? Here is the summary:
- Results converge as steps (`-s`) are increased (except for `K_DPM_2_A` and `K_EULER_A`). Often at ≥ `-s100`, but may require ≥ `-s700`.
- Producing a batch of candidate images at low (`-s8` to `-s30`) step counts can save you hours of computation.
- `K_HEUN` and `K_DPM_2` converge in fewer steps (but are slower per step).
- `K_DPM_2_A` and `K_EULER_A` incorporate a lot of creativity and variability.
</Card>
## Sampler Performance Overview
<CardGrid>
<Card title="Speed (it/s)" icon="setting">
*(Tested on M1 Max 64GB, 512x512, 3 sample average)*
| Sampler | it/s |
| :--- | :--- |
| `DDIM` | 1.89 |
| `PLMS` | 1.86 |
| `K_EULER` | 1.86 |
| `K_LMS` | **1.91** (Fastest) |
| `K_EULER_A` | 1.86 |
| `K_HEUN` | 0.95 *(Slower)* |
| `K_DPM_2` | 0.95 *(Slower)* |
| `K_DPM_2_A` | 0.95 *(Slower)* |
</Card>
<Card title="Suggestions" icon="star">
For most use cases, `K_LMS`, `K_HEUN` and `K_DPM_2` are the best choices.
While `K_HEUN` and `K_DPM_2` run half as fast, they tend to converge twice as quickly as `K_LMS`.
At very low steps (≤ `-s8`), `K_HEUN` and `K_DPM_2` are not recommended. Use `K_LMS` instead.
For high variability between steps, use `K_EULER_A` (which runs twice as fast as `K_DPM_2_A`).
</Card>
</CardGrid>
---
## Sampler Results by Subject
Let's start by choosing a prompt and using it with each of our 8 samplers, running it for 10, 20, 30, 40, 50 and 100 steps.
### Anime
> `"an anime girl" -W512 -H512 -C7.5 -S3031912972`
![Anime Comparison Grid](https://user-images.githubusercontent.com/50542132/191868725-7f7af991-e254-4c1f-83e7-bed8c9b2d34f.png)
Immediately, you can notice results tend to converge — that is, as `-s` (step) values increase, images look more and more similar until there comes a point where the image no longer changes.
You can also notice how `DDIM` and `PLMS` eventually tend to converge to K-sampler results as steps are increased. Among K-samplers, `K_HEUN` and `K_DPM_2` seem to require the fewest steps to converge, and even at low step counts they are good indicators of the final result. Finally, `K_DPM_2_A` and `K_EULER_A` seem to do a bit of their own thing and don't keep much similarity with the rest of the samplers.
### Nature
Now, these results seem interesting, but do they hold for other topics? Let's try!
> `"valley landscape wallpaper, d&d art, fantasy, painted, 4k, high detail, sharp focus, washed colors, elaborate excellent painted illustration" -W512 -H512 -C7.5 -S1458228930`
![Nature Comparison Grid](https://user-images.githubusercontent.com/50542132/191868763-b151c69e-0a72-4cf1-a151-5a64edd0c93e.png)
With nature, you can see how initial results are even more indicative of the final result — more so than with characters/people. `K_HEUN` and `K_DPM_2` are again the quickest indicators, almost right from the start. Results also converge faster (e.g. `K_HEUN` converged at `-s21`).
### Food
> `"a hamburger with a bowl of french fries" -W512 -H512 -C7.5 -S4053222918`
![Food Comparison Grid](https://user-images.githubusercontent.com/50542132/191868898-98801a62-885f-4ea1-aee8-563503522aa9.png)
Again, `K_HEUN` and `K_DPM_2` take the fewest number of steps to be good indicators of the final result. `K_DPM_2_A` and `K_EULER_A` seem to incorporate a lot of creativity/variability, capable of producing rotten hamburgers, but also of adding lettuce to the mix. And they're the only samplers that produced an actual 'bowl of fries'!
### Animals
> `"grown tiger, full body" -W512 -H512 -C7.5 -S3721629802`
![Animal Comparison Grid](https://user-images.githubusercontent.com/50542132/191868870-9e3b7d82-b909-429f-893a-13f6ec343454.png)
`K_HEUN` and `K_DPM_2` once again require the least number of steps to be indicative of the final result (around `-s30`), while other samplers are still struggling with several tails or malformed back legs.
It also takes longer to converge (for comparison, `K_HEUN` required around 150 steps to converge). This is normal, as producing human/animal faces/bodies is one of the things the model struggles the most with. For these topics, running for more steps will often increase coherence within the composition.
### People
> `"Ultra realistic photo, (Miranda Bloom-Kerr), young, stunning model, blue eyes, blond hair, beautiful face, intricate, highly detailed, smooth, art by artgerm and greg rutkowski and alphonse mucha, stained glass" -W512 -H512 -C7.5 -S2131956332`. *(This time, we will go up to 300 steps).*
![People Comparison Grid 1](https://user-images.githubusercontent.com/50542132/191871743-6802f199-0ffd-4986-98c5-df2d8db30d18.png)
Observing the results, it again takes longer for all samplers to converge (`K_HEUN` took around 150 steps), but we can observe good indicative results much earlier (see: `K_HEUN`). Conversely, `DDIM` and `PLMS` are still undergoing moderate changes (see: lace around her neck), even at `-s300`.
In fact, as we can see in this other experiment, some samplers can take 700+ steps to converge when generating people.
![People Comparison Grid 2](https://user-images.githubusercontent.com/50542132/191992123-7e0759d6-6220-42c4-a961-88c7071c5ee6.png)
Note also the point of convergence may not be the most desirable state (e.g. you might prefer an earlier version of the face that is more rounded), but it will probably be the most coherent regarding arms/hands/face attributes. You can always merge different images with a photo editing tool and pass it through `img2img` to smoothen the composition.
---
## Batch Generation Speedup
This realization about convergence is very useful because it means you don't need to create a batch of 100 images (`-n100`) at `-s100` just to choose your favorite 2 or 3 images.
You can produce the same 100 images at `-s10` to `-s30` using a K-sampler (since they converge faster), get a rough idea of the final result, choose your 2 or 3 favorite ones, and then run `-s100` on those specific images to polish details. This technique is **3-8x as quick**.
:::tip[Time Savings Example]
Assuming 60 seconds per 100 steps:
- **Method A:** 60s * 100 images = **6000s** (100 images at `-s100`, manually picking 3 favorites). Total time: **1 hour and 40 minutes.**
- **Method B:** 6s * 100 images + 60s * 3 images = **780s** (100 images at `-s10`, manually picking 3 favorites, and running those 3 at `-s100` to polish details). Total time: **13 minutes.**
:::
## Three Key Takeaways
Finally, it is relevant to mention that, in general, there are 3 important moments in the process of image formation as steps increase:
<Steps>
1. **The Indicator Stage:**
The earliest point at which an image becomes a good indicator of the final result. This is useful for batch generation at low step values to preview outputs before committing to higher steps.
2. **The Coherence Stage:**
The point at which an image becomes coherent, even if different from the final converged result. This is useful for low-step batch generation where quality is improved via other techniques (like inpainting) rather than raw step count.
3. **The Convergence Stage:**
The point at which an image fully converges and stops changing.
</Steps>
:::note[Workflow Dictates Strategy]
Remember that your workflow/strategy should define your optimal number of steps, even for the same prompt and seed. For example, if you seek full convergence, you may run `K_LMS` for `-s200`. However, running `K_LMS` for `-s20` (taking one-tenth the time) may perform just as well if your workflow includes adding small missing details via `img2img`.
:::
![Low Step Sampler Comparison](https://user-images.githubusercontent.com/50542132/192046823-2714cb29-bbf3-4eb1-9213-e27a0963905c.png)
@@ -0,0 +1,138 @@
---
title: Prompting Syntax
lastUpdated: 2026-03-30
sidebar:
order: 3
---
import { Card, LinkCard, CardGrid } from '@astrojs/starlight/components';
<CardGrid>
<LinkCard
title="Prompting Guide"
href="../prompting-guide"
description="Learn how to write effective prompts for InvokeAI."
/>
<LinkCard
title="Dynamic Prompting"
href="../dynamic-prompting"
description="Learn how to create many prompt variations from a single template."
/>
</CardGrid>
InvokeAI supports Compel-style prompt weighting and prompt functions for `SD 1.5` and `SDXL` text conditioning workflows. Recent model families, including `FLUX`, `Z-Image`, `CogView4`, and `Qwen Image`, bypass Compel and do not use the syntax documented on this page. This page documents syntax for those Compel-based workflows only. If you want general advice on writing better prompts, start with [Prompting Guide](../prompting-guide).
:::note[Compatibility note]
If a weighted prompt seems to be ignored, check whether you are using an `SD 1.5` or `SDXL` workflow. Compel syntax on this page does not apply to newer model families such as `FLUX`, `Z-Image`, `CogView4`, and `Qwen Image`.
:::
## Quick reference
<Card title="Supported syntax" icon="rocket">
- Increase a single word: `trees+`
- Decrease a single word: `fog-`
- Weight a phrase: `(golden hour light)+`
- Use an exact numeric weight: `(cinematic lighting)1.25`
- Nest weights: `(portrait with (blue eyes)1.3)1.1`
- Blend prompts: `("portrait photo", "oil painting").blend(0.7, 0.3)`
- Conjoin clauses: `("red silk dress", "studio portrait", "soft rim light").and()`
- Escape literal parentheses: `colored pencil \(medium\)`
</Card>
## Attention weighting with `+` and `-`
Append `+` to increase influence, or `-` to reduce it.
```text
freckles+
background crowd-
(soft rim light)++
```
Rules of thumb:
- Single words can be weighted directly.
- Multi-word phrases should be wrapped in parentheses.
- Each additional `+` compounds upward.
- Each additional `-` compounds downward in roughly 10% steps.
:::tip[Start small]
One or two steps is usually enough. Extreme weighting can overpower the rest of the prompt.
:::
## Numeric weights
Use numeric weights when you want precise control instead of repeated plus or minus markers.
```text
(cinematic lighting)1.25
(background crowd)0.8
(sharp focus)1.1
```
Guidelines:
- `1` is neutral.
- Values greater than `1` increase emphasis.
- Values between `0` and `1` reduce emphasis.
- Wrap the weighted phrase in parentheses.
## Grouping and nesting
You can group phrases and apply weight to the whole group, then nest another weighted phrase inside it.
```text
(portrait with (blue eyes)1.3)1.1
```
In this example, the outer group strengthens the whole phrase, and the inner group gives `blue eyes` even more emphasis.
## Blend prompts with `.blend()`
Use `.blend()` to mix the meaning of two or more prompts.
```text
("portrait photo, 85mm lens", "oil painting, visible brushstrokes").blend(0.7, 0.3)
```
This is most useful for combining concepts or styles that you want balanced deliberately.
Tips:
- Provide one weight for each prompt argument.
- Keeping the weights near a total of `1` makes the result easier to reason about.
- Quoted arguments are the safest choice, especially when the prompts contain commas.
## Combine clauses with `.and()`
Use `.and()` when you want separate prompt clauses encoded individually instead of as one long comma-separated sentence.
```text
("red silk dress", "studio portrait", "soft rim light").and()
```
This can behave differently from:
```text
red silk dress, studio portrait, soft rim light
```
If a normal prompt keeps collapsing ideas together, `.and()` is worth testing.
## Escape literal parentheses
Unescaped parentheses are treated as prompt syntax. If you want actual parentheses in the text, escape them with backslashes.
```text
colored pencil \(medium\)
portrait \(realistic\) (high quality)1.2
A bear \(with razor-sharp teeth\) in a forest
```
Use unescaped parentheses only when you mean grouping or weighting.
## Related pages
- For practical prompt-writing advice, read [Prompting Guide](../prompting-guide).
- For prompt expansion and permutations, read [Dynamic Prompting](../dynamic-prompting).
@@ -0,0 +1,180 @@
---
title: Prompting Guide
lastUpdated: 2026-03-30
sidebar:
order: 2
---
import { Card, CardGrid, Steps, LinkCard } from '@astrojs/starlight/components';
<CardGrid>
<LinkCard
title="Prompting Syntax"
href="../prompt-syntax"
description="Learn how to weight prompt terms, blend concepts, and use prompt conjunctions for more control."
/>
<LinkCard
title="Dynamic Prompting"
href="../dynamic-prompting"
description="Learn how to create many prompt variations from a single template."
/>
</CardGrid>
Prompting in InvokeAI works best when you describe the image clearly, then refine only the parts that matter. This page focuses on practical prompt-writing habits.
<CardGrid>
<Card title="Subject" icon="seti:image">
Start with the main thing you want to see: a character, object, scene, or action.
</Card>
<Card title="Style or Medium" icon="pencil">
Add the visual language: photograph, watercolor, oil painting, 3D render, anime illustration, and so on.
</Card>
<Card title="Lighting and Composition" icon="setting">
Describe the camera angle, framing, lighting, environment, color palette, or mood that will shape the image.
</Card>
<Card title="Detail and Finish" icon="star">
Add a few high-value quality cues such as fabric texture, shallow depth of field, natural skin texture, or painterly brushwork.
</Card>
</CardGrid>
A simple pattern that works well is:
`subject, style or medium, lighting or composition, a few important details`
Not every prompt needs every category. Start simple, then add detail only when the model needs more direction.
## Positive and negative prompts
<CardGrid>
<Card title="Positive Prompt" icon="seti:default">
Use the positive prompt to describe what you want the model to create. Put the most important idea early and keep the wording concrete.
</Card>
<Card title="Negative Prompt" icon="document">
Use the negative prompt to remove recurring problems or unwanted traits. Keep it short and targeted instead of pasting a giant list into every generation.
</Card>
</CardGrid>
Good negative prompts usually name specific failure modes: `blurry`, `distorted hands`, `low detail`, `extra limbs`.
:::tip[Negative prompts are strong]
A negative term can suppress nearby concepts too. If you negate something broad like `green` or `moss`, you may also weaken grass, foliage, or other related ideas.
:::
## A practical prompting workflow
<Steps>
1. Start with the core image
Write the clearest version of the image you want before adding stylistic extras.
2. Add style and composition
Once the subject is right, add medium, lens, lighting, mood, background, or framing details.
3. Test with a fixed seed
When you are learning what a prompt change does, keep the seed stable so you can compare results directly.
4. Change one thing at a time
If you add five new terms at once, you will not know which one helped.
5. Escalate only when needed
If the result is close but one element is too weak or too strong, move to [Prompting Syntax](../prompt-syntax) for weighting. If you want lots of variations, use [Dynamic Prompting](../dynamic-prompting).
</Steps>
Here is the same idea refined in stages:
```text
portrait of a woman
portrait of a woman, studio photograph, soft key light
portrait of a woman, studio photograph, soft key light, 85mm lens, shallow depth of field, natural skin texture
```
## Write for the model you are using
The same prompt can behave very differently across models.
- Photo-oriented models respond well to camera, lens, lighting, and texture language.
- Illustration models often respond better to medium, art direction, and shape language.
- Specialty models may expect specific trigger words, subjects, or styles from their own model card.
- If a prompt works beautifully on one model and poorly on another, that does not always mean the prompt is bad. The model may just speak a different visual language.
## When advanced syntax helps
Reach for advanced syntax when a normal comma-separated prompt is almost right, but you need more control.
- Use [Prompting Syntax](../prompt-syntax) when one term needs more or less influence.
- Use `.blend()` when you want to mix concepts or styles deliberately.
- Use `.and()` when you want separate prompt clauses encoded individually.
- Use [Dynamic Prompting](../dynamic-prompting) when you want many prompt variations from one template.
## Common mistakes
- Packing too many unrelated ideas into one prompt.
- Using long generic quality-word lists before you know the base prompt works.
- Treating the negative prompt as a trash can for every bad outcome.
- Expecting identical behavior across models, schedulers, and workflows.
- Changing prompt, model, seed, and settings all at once while troubleshooting.
## Example prompts
### Photographic portrait
**Positive prompt**
```text
editorial portrait of a woman in a charcoal coat, studio photograph, soft key light, subtle rim light, 85mm lens, shallow depth of field, natural skin texture
```
**Negative prompt**
```text
blurry, low detail, waxy skin, extra fingers
```
### Environment concept art
**Positive prompt**
```text
ancient stone temple built into a cliffside, fantasy concept art, misty sunrise, towering scale, moss-covered stairs, cinematic atmosphere
```
**Negative prompt**
```text
flat lighting, low contrast, muddy details
```
### Product-style render
**Positive prompt**
```text
sleek ceramic teapot on a matte stone surface, product photography, clean studio lighting, soft shadow, high detail, minimal background
```
**Negative prompt**
```text
cluttered background, distortion, duplicate objects
```
### Stylized illustration
**Positive prompt**
```text
fox courier crossing a rainy city street, storybook illustration, bold shapes, glowing shop signs, reflective pavement, warm and cool color contrast
```
**Negative prompt**
```text
photorealistic, dull colors, low detail
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@@ -0,0 +1,95 @@
---
title: Docker
---
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'
import SystemRequirementsLink from '@components/SystemRequirmentsLink.astro'
<SystemRequirementsLink />
:::note[Operating Systems and GPU Support]
<Tabs syncKey="operatingSystem">
<TabItem label="Windows" icon="seti:windows">
Docker Desktop on Windows [includes GPU support](https://www.docker.com/blog/wsl-2-gpu-support-for-docker-desktop-on-nvidia-gpus/).
</TabItem>
<TabItem label="MacOS" icon="apple">
Docker can not access the GPU on macOS, so your generation speeds will be slow. Use the [launcher](../../start-here/installation) instead.
</TabItem>
<TabItem label="Linux" icon="linux">
Configure Docker to access your machine's GPU.
Follow the [NVIDIA](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) or [AMD](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html) documentation.
</TabItem>
</Tabs>
:::
## TL;DR
Ensure your Docker setup is able to use your GPU. Then:
```bash
docker run --runtime=nvidia --gpus=all --publish 9090:9090 ghcr.io/invoke-ai/invokeai
```
Once the container starts up, open [http://localhost:9090](http://localhost:9090) in your browser, install some models, and start generating.
## Build-It-Yourself
All the docker materials are located inside the [docker](https://github.com/invoke-ai/InvokeAI/tree/main/docker) directory in the Git repo.
```bash
cd docker
cp .env.sample .env
docker compose up
```
We also ship the `run.sh` convenience script. See the `docker/README.md` file for detailed instructions on how to customize the docker setup to your needs.
### Prerequisites
#### Install [Docker](https://github.com/santisbon/guides#docker)
On the [Docker Desktop app](https://docs.docker.com/get-docker/), go to `Preferences` -> `Resources` -> `Advanced`. Increase the CPUs and Memory to avoid this [Issue](https://github.com/invoke-ai/InvokeAI/issues/342). You may need to increase Swap and Disk image size too.
### Setup
Set up your environment variables. In the `docker` directory, make a copy of `.env.sample` and name it `.env`. Make changes as necessary.
Any environment variables supported by InvokeAI can be set here - please see the [configuration docs](/configuration/invokeai-yaml/) for further detail.
At the very least, you might want to set the `INVOKEAI_ROOT` environment variable
to point to the location where you wish to store your InvokeAI models, configuration, and outputs.
| Environment Variable | Default value | Description |
| --- | --- | --- |
| `INVOKEAI_ROOT` | `~/invokeai` | **Required** - the location of your InvokeAI root directory. It will be created if it does not exist. |
| `HUGGING_FACE_HUB_TOKEN` | | InvokeAI will work without it, but some of the integrations with HuggingFace (like downloading from models from private repositories) may not work |
| `GPU_DRIVER` | `cuda` | Optionally change this to `rocm` to build the image for AMD GPUs. NOTE: Use the `build.sh` script to build the image for this to take effect. |
#### Build the Image
Use the standard `docker compose build` command from within the `docker` directory.
If using an AMD GPU:
a: set the `GPU_DRIVER=rocm` environment variable in `docker-compose.yml` and continue using `docker compose build` as usual, or
b: set `GPU_DRIVER=rocm` in the `.env` file and use the `build.sh` script, provided for convenience
#### Run the Container
Use the standard `docker compose up` command, and generally the `docker compose` [CLI](https://docs.docker.com/compose/reference/) as usual.
Once the container starts up (and configures the InvokeAI root directory if this is a new installation), you can access InvokeAI at [http://localhost:9090](http://localhost:9090)
## Troubleshooting / FAQ
<details>
<summary>"I am running Windows under WSL2, and am seeing a 'no such file or directory' error."</summary>
Your `docker-entrypoint.sh` might have has Windows (CRLF) line endings, depending how you cloned the repository.
To solve this, change the line endings in the `docker-entrypoint.sh` file to `LF`. You can do this in VSCode
(`Ctrl+P` and search for "line endings"), or by using the `dos2unix` utility in WSL.
Finally, you may delete `docker-entrypoint.sh` followed by `git pull; git checkout docker/docker-entrypoint.sh`
to reset the file to its most recent version.
For more information on this issue, see [Docker Desktop documentation](https://docs.docker.com/desktop/troubleshoot/topics/#avoid-unexpected-syntax-errors-use-unix-style-line-endings-for-files-in-containers)
</details>
@@ -0,0 +1,128 @@
---
title: FP8 Storage
sidebar:
order: 3
---
import { Steps } from '@astrojs/starlight/components';
FP8 Storage cuts a model's VRAM footprint roughly in half by keeping weights on the GPU in 8-bit floating-point format (`float8_e4m3fn`). During inference, each layer's weights are cast on-the-fly back up to the compute precision (FP16/BF16), then cast back to FP8 after the forward pass — so quality is largely preserved.
It pairs well with [Low-VRAM mode](/configuration/low-vram-mode/): low-VRAM mode streams layers between RAM and VRAM, while FP8 Storage shrinks the layers themselves.
:::caution[For full precision models only]
FP8 Storage only applies to **full precision** checkpoints (FP16 / BF16 / FP32). It is **silently a no-op** for already-quantized formats — **GGUF**, **NF4**, and **int8** checkpoints carry their own storage precision and the loader returns a different module type that the FP8 layer cast does not touch. If your model is already quantized, the toggle has no effect; use the full-precision variant of the model if you want to enable FP8 Storage.
:::
## Requirements
- **Nvidia GPU on Windows or Linux.** FP8 Storage uses CUDA tensor types and is silently disabled on CPU and MPS.
- **CUDA 12.x and recent PyTorch.** The `float8_e4m3fn` dtype was added in PyTorch 2.1 — InvokeAI's bundled versions satisfy this.
There is no hardware requirement for FP8 *compute* — InvokeAI casts back to FP16/BF16 for math. This means FP8 Storage works on GPUs that do not natively support FP8 matmul (e.g. RTX 30-series), at a small per-step throughput cost.
## Hardware support tiers
InvokeAI's FP8 path stores weights in FP8 and casts them back to BF16/FP16 on each forward pass via its own `register_forward_pre_hook` / `register_forward_hook` wrappers (the same skip list as diffusers' `apply_layerwise_casting`, but applied to every `nn.Module` — including diffusers `ModelMixin` subclasses — so it composes correctly with InvokeAI's `CustomLinear` and partial loading). The practical benefit of toggling FP8 Storage depends on what your GPU can do natively. There are three tiers:
### RTX 30-series and older Ampere workstation cards — VRAM win only
The toggle works as advertised: the UNet / transformer drops by roughly 50% on the GPU. Per-step latency is the same or marginally slower because every forward pass adds an FP8 → BF16 cast on entry and a BF16 → FP8 cast on exit. This is the **largest target group**: 3090 owners squeezing FLUX into 24 GB benefit the most.
### RTX 40-series, RTX 50-series, and Hopper — VRAM win today, compute win possible later
These GPUs have native FP8 tensor cores. The toggle still buys you the same ~50% VRAM reduction today, because the forward pass still runs in BF16 — the hook casts weights back up to compute precision before each layer. If InvokeAI later wires up a true FP8 matmul path (e.g. via `torchao`), the same toggle will *also* unlock compute speedups on this hardware. Until then, treat the benefit as "VRAM only, same as Ampere".
### Older CUDA cards — still a VRAM win
`float8_e4m3fn` is a pure storage dtype in PyTorch and works on any CUDA device, so pre-Ampere cards (GTX 16-series, RTX 20-series, etc.) get the same ~50% VRAM reduction as Ampere. There are no native FP8 tensor cores on these GPUs, so the throughput trade-off is the same as on the 30-series: cast in, compute in BF16/FP16, cast back out.
### MPS and CPU — no-op
FP8 Storage is silently disabled on anything that is not CUDA. On CPU PyTorch *technically* supports FP8 dtypes, but the cast operations are software-emulated and end up costing more than the memory savings buy back, so InvokeAI gates the entire path on `device.type == "cuda"`. If you toggle it on CPU or MPS, the loader skips the cast and returns the model unchanged with no log line.
## Enabling FP8 Storage
FP8 Storage is a **per-model setting**, configured from the Model Manager:
<Steps>
1. Open the **Model Manager**.
2. Select a model (Main, ControlNet, or T2I-Adapter).
3. Under **Default Settings**, toggle **FP8 Storage (Save VRAM)**.
4. Click **Save**.
</Steps>
The setting takes effect on the next load. If the model is already in the cache, InvokeAI evicts the cached copy automatically so the new setting applies — even if a generation is currently using the model (the eviction is deferred until the generation finishes).
:::tip[When to enable]
Enable FP8 Storage on large models that don't fit comfortably in VRAM — FLUX dev/Klein, large SDXL checkpoints, ControlNet-XL adapters. For smaller SD1 / SD2 models, the savings are negligible and not worth the small precision trade-off.
:::
## What FP8 Storage applies to
FP8 Storage is **only** applied to layers where the precision trade-off is acceptable:
| Model type | FP8 applied? |
| ----------------------------- | -------------------------------------- |
| Main models (SD1, SD2, SDXL) | Yes |
| FLUX.1 / FLUX.2 Klein | Yes |
| ControlNet, T2I-Adapter | Yes |
| VAE | No — visible decode-quality regression |
| Text encoders, tokenizers | No — small models, no benefit |
| Z-Image (any variant) | No — dtype mismatch with skipped layers|
| LoRA, ControlLoRA | No — patched into base, not run alone |
Within a supported model, **norm layers, position/patch embeddings, and `proj_in`/`proj_out` are skipped** so precision-sensitive tiny learned scalars (e.g. FLUX `RMSNorm.scale`) aren't crushed to FP8. This mirrors the diffusers default skip list.
## Quality trade-offs
FP8 Storage is **near-lossless** for most workloads because:
- Norms and embeddings (the precision-sensitive layers) are skipped.
- The actual matmul still happens in FP16/BF16 — FP8 is only the on-GPU storage format.
That said, some artifacts have been reported on:
- **VAEs** — never cast (the toggle has no effect on VAE submodels).
- **Heavy LoRA stacks** — patching is unaffected, but very precision-sensitive LoRAs may show slight drift. Compare a side-by-side if your workflow depends on subtle LoRA behavior.
If you see unexpected quality regressions, disable FP8 Storage on the affected model and re-run.
## Combining with Low-VRAM mode
**FP8 + partial loading**: fully supported. FP8 Storage shrinks the layers; partial loading streams them between RAM and VRAM as needed. Use both on tight VRAM budgets.
(For why FP8 Storage doesn't stack on top of GGUF / NF4 / int8 checkpoints, see the callout at the top of this page.)
## Troubleshooting
### "I toggled FP8 Storage but VRAM usage didn't change"
The cache eviction is immediate for idle models, but **deferred until the next unlock** if the model is mid-generation. Wait for the current generation to finish, then start a new one — the next load will use the new setting.
If VRAM still hasn't dropped:
- Check the InvokeAI log for `FP8 layerwise casting enabled for <model name>`. If the line isn't there, the model is on the exclusion list (VAE, text encoder, Z-Image, LoRA — see table above).
- Confirm you are on CUDA. FP8 Storage is silently disabled on CPU and MPS.
### Quality regression on a specific model
Disable FP8 Storage for that model in Model Manager and reload. If quality is restored, the model has FP8-sensitive layers that fall outside the default skip list. Please open an issue with the model name and a side-by-side comparison.
### "RuntimeError: ... float8_e4m3fn ..."
You're on a PyTorch version that predates FP8 support. Reinstall InvokeAI using the official launcher — the bundled torch version supports FP8.
### Reporting an FP8 issue
If FP8 Storage misbehaves — crash, quality regression, OOM that shouldn't happen — please [open a GitHub issue](https://github.com/invoke-ai/InvokeAI/issues/new/choose) and include:
- **What you did**: the workflow / generation step that triggered the problem, and whether it reproduces every time.
- **Model**: exact name and variant (e.g. "FLUX.2 Klein 9B Diffusers", "SDXL Base 1.0 single-file"), and whether the file is a full-precision checkpoint or already quantized (GGUF / NF4 / int8).
- **LoRAs**: whether any LoRAs (or ControlLoRAs) are stacked on the model, and how many.
- **Other toggles**: Low-VRAM mode on/off, any `cpu_only` text encoder setting, configured VRAM limit.
- **GPU**: model and VRAM size (e.g. "RTX 3090 24 GB", "RTX 4070 Ti 12 GB").
- **OS**: Windows or Linux, plus driver / CUDA version if you have it.
- **Logs**: the InvokeAI log around the failure — in particular the `FP8 layerwise casting enabled for <model>` line (or its absence) and any traceback.
A side-by-side image comparison (FP8 on vs. FP8 off, same seed) is extremely useful for quality regressions.
@@ -0,0 +1,212 @@
---
title: YAML Config
sidebar:
order: 1
---
import { FileTree } from '@astrojs/starlight/components'
import SettingsDocs from '@lib/components/SettingsDocs.astro'
Runtime settings, including the location of files and directories, memory usage, and performance, are managed via the `invokeai.yaml` config file or environment variables. A subset of settings may be set via commandline arguments.
Settings sources are used in this order:
- CLI args
- Environment variables
- `invokeai.yaml` settings
- Fallback: defaults
### InvokeAI Root Directory
On startup, InvokeAI searches for its "root" directory. This is the directory that contains models, images, the database, and so on. It also contains a configuration file called `invokeai.yaml`.
<FileTree>
- models/
- outputs/
- databases/
- workflow_thumbnails/
- style_presets/
- nodes/
- configs/
- invokeai.example.yaml
- **invokeai.yaml**
</FileTree>
InvokeAI searches for the root directory in this order:
1. The `--root <path>` CLI arg.
2. The environment variable INVOKEAI_ROOT.
3. The directory containing the currently active virtual environment.
4. Fallback: a directory in the current user's home directory named `invokeai`.
### InvokeAI Configuration File
Inside the root directory, we read settings from the `invokeai.yaml` file.
It has two sections - one for internal use and one for user settings:
```yaml
# Internal metadata - do not edit:
schema_version: 4.0.2
# Put user settings here - see https://invoke.ai/configuration/invokeai-yaml/:
host: 0.0.0.0 # serve the app on your local network
models_dir: D:\invokeai\models # store models on an external drive
precision: float16 # always use fp16 precision
```
The settings in this file will override the defaults. You only need
to change this file if the default for a particular setting doesn't
work for you.
You'll find an example file next to `invokeai.yaml` that shows the default values.
Some settings, like [Model Marketplace API Keys], require the YAML
to be formatted correctly. Here is a [basic guide to YAML files].
#### Custom Config File Location
You can use any config file with the `--config` CLI arg. Pass in the path to the `invokeai.yaml` file you want to use.
Note that environment variables will trump any settings in the config file.
#### Model Marketplace API Keys
Some model marketplaces require an API key to download models. You can provide a URL pattern and appropriate token in your `invokeai.yaml` file to provide that API key.
The pattern can be any valid regex (you may need to surround the pattern with quotes):
```yaml
remote_api_tokens:
# Any URL containing `models.com` will automatically use `your_models_com_token`
- url_regex: models.com
token: your_models_com_token
# Any URL matching this contrived regex will use `some_other_token`
- url_regex: '^[a-z]{3}whatever.*\.com$'
token: some_other_token
```
The provided token will be added as a `Bearer` token to the network requests to download the model files. As far as we know, this works for all model marketplaces that require authorization.
:::tip[Hugging face Models]
If you get an error when installing a HF model using a URL instead of repo id, you may need to [set up a HF API token](https://huggingface.co/settings/tokens) and add an entry for it under `remote_api_tokens`. Use `huggingface.co` for `url_regex`.
:::
#### Model Hashing
Models are hashed during installation, providing a stable identifier for models across all platforms. Hashing is a one-time operation.
```yaml
hashing_algorithm: blake3_single # default value
```
You might want to change this setting, depending on your system:
- `blake3_single` (default): Single-threaded - best for spinning HDDs, still OK for SSDs
- `blake3_multi`: Parallelized, memory-mapped implementation - best for SSDs, terrible for spinning disks
- `random`: Skip hashing entirely - fastest but of course no hash
During the first startup after upgrading to v4, all of your models will be hashed. This can take a few minutes.
Most common algorithms are supported, like `md5`, `sha256`, and `sha512`. These are typically much, much slower than either of the BLAKE3 variants.
#### Path Settings
These options set the paths of various directories and files used by InvokeAI. Any user-defined paths should be absolute paths.
#### Image Subfolder Strategy
By default, generated images are stored in a single flat directory under `outputs/images/`. The `image_subfolder_strategy` setting lets you organize newly-created images into subfolders automatically. You can edit this setting in `invokeai.yaml` or, as an admin user, in the Settings panel.
```yaml
image_subfolder_strategy: flat # default value
```
Available strategies:
| Strategy | Example Path | Description |
| -------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `flat` | `outputs/images/abc123.png` | Store images directly in the images directory. |
| `date` | `outputs/images/2026/03/17/abc123.png` | Organize images by creation date. |
| `type` | `outputs/images/general/abc123.png` | Organize images by image category. |
| `hash` | `outputs/images/ab/abc123.png` | Use the first two characters of the image UUID for filesystem performance with large collections. |
Changing this setting only affects newly-created images. Existing images remain in their current locations unless you run [Image Storage Maintenance](/features/image-storage-maintenance/).
#### Logging
Several different log handler destinations are available, and multiple destinations are supported by providing a list:
```yaml
log_handlers:
- console
- syslog=localhost
- file=/var/log/invokeai.log
```
- `console` is the default. It prints log messages to the command-line window from which InvokeAI was launched.
- `syslog` is only available on Linux and Macintosh systems. It uses
the operating system's "syslog" facility to write log file entries
locally or to a remote logging machine. `syslog` offers a variety
of configuration options:
```yaml
syslog=/dev/log` - log to the /dev/log device
syslog=localhost` - log to the network logger running on the local machine
syslog=localhost:512` - same as above, but using a non-standard port
syslog=fredserver,facility=LOG_USER,socktype=SOCK_DRAM`
- Log to LAN-connected server "fredserver" using the facility LOG_USER and datagram packets.
```
- `http` can be used to log to a remote web server. The server must be
properly configured to receive and act on log messages. The option
accepts the URL to the web server, and a `method` argument
indicating whether the message should be submitted using the GET or
POST method.
```yaml
http=http://my.server/path/to/logger,method=POST
```
The `log_format` option provides several alternative formats:
- `color` - default format providing time, date and a message, using text colors to distinguish different log severities
- `plain` - same as above, but monochrome text only
- `syslog` - the log level and error message only, allowing the syslog system to attach the time and date
- `legacy` - a format similar to the one used by the legacy 2.3 InvokeAI releases.
### Environment Variables
All settings may be set via environment variables by prefixing `INVOKEAI_`
to the variable name. For example, `INVOKEAI_HOST` would set the `host`
setting.
For non-primitive values, pass a JSON-encoded string:
```sh
export INVOKEAI_REMOTE_API_TOKENS='[{"url_regex":"modelmarketplace", "token": "12345"}]'
```
We suggest using `invokeai.yaml`, as it is more user-friendly.
### CLI Args
A subset of settings may be specified using CLI args:
- `--root`: specify the root directory
- `--config`: override the default `invokeai.yaml` file location
### Low-VRAM Mode
See the [Low-VRAM mode docs][low-vram] for details on enabling this feature.
### All Settings
The full settings reference is below. Additional explanations for selected settings appear earlier on this page.
<SettingsDocs />
[basic guide to yaml files]: https://circleci.com/blog/what-is-yaml-a-beginner-s-guide/
[Model Marketplace API Keys]: #model-marketplace-api-keys
[low-vram]: /configuration/low-vram-mode
@@ -0,0 +1,182 @@
---
title: Low-VRAM mode
sidebar:
order: 2
---
As of v5.6.0, Invoke has a low-VRAM mode. It works on systems with dedicated GPUs (Nvidia GPUs on Windows/Linux and AMD GPUs on Linux).
This allows you to generate even if your GPU doesn't have enough VRAM to hold full models. Most users should be able to run even the beefiest models - like the ~24GB unquantised FLUX dev model.
## Enabling Low-VRAM mode
Low-VRAM mode is **enabled by default** via the `enable_partial_loading: true` setting in `invokeai.yaml`. No action is required to turn it on.
**Windows users should also [disable the Nvidia sysmem fallback](#disabling-nvidia-sysmem-fallback-windows-only)**.
It is possible to fine-tune the settings for best performance or if you still get out-of-memory errors (OOMs).
If you want to disable partial loading (e.g. on systems with plenty of VRAM where full loading is faster), add this line to your `invokeai.yaml` and restart Invoke:
```yaml
enable_partial_loading: false
```
:::tip[How to find `invokeai.yaml`]
The `invokeai.yaml` configuration file lives in your install directory. To access it, run the **Invoke Community Edition** launcher and click the install location. This will open your install directory in a file explorer window.
You'll see `invokeai.yaml` there and can edit it with any text editor. After making changes, restart Invoke.
If you don't see `invokeai.yaml`, launch Invoke once. It will create the file on its first startup.
:::
## Details and fine-tuning
Low-VRAM mode involves 4 features, each of which can be configured or fine-tuned:
- Partial model loading (`enable_partial_loading`)
- PyTorch CUDA allocator config (`pytorch_cuda_alloc_conf`)
- Dynamic RAM and VRAM cache sizes (`max_cache_ram_gb`, `max_cache_vram_gb`)
- Working memory (`device_working_mem_gb`)
- Keeping a RAM weight copy (`keep_ram_copy_of_weights`)
Read on to learn about these features and understand how to fine-tune them for your system and use-cases.
### Partial model loading
Invoke's partial model loading works by streaming model "layers" between RAM and VRAM as they are needed.
When an operation needs layers that are not in VRAM, but there isn't enough room to load them, inactive layers are offloaded to RAM to make room.
#### Enabling partial model loading
Partial model loading is enabled by default. The corresponding setting in `invokeai.yaml` is:
```yaml
enable_partial_loading: true
```
Set it to `false` to disable partial loading.
### PyTorch CUDA allocator config
The PyTorch CUDA allocator's behavior can be configured using the `pytorch_cuda_alloc_conf` config. Tuning the allocator configuration can help to reduce the peak reserved VRAM. The optimal configuration is dependent on many factors (e.g. device type, VRAM, CUDA driver version, etc.), but switching from PyTorch's native allocator to using CUDA's built-in allocator works well on many systems. To try this, add the following line to your `invokeai.yaml` file:
```yaml
pytorch_cuda_alloc_conf: "backend:cudaMallocAsync"
```
A more complete explanation of the available configuration options is [here](https://pytorch.org/docs/stable/notes/cuda.html#optimizing-memory-usage-with-pytorch-cuda-alloc-conf).
### Dynamic RAM and VRAM cache sizes
Loading models from disk is slow and can be a major bottleneck for performance. Invoke uses two model caches - RAM and VRAM - to reduce loading from disk to a minimum.
By default, Invoke manages these caches' sizes dynamically for best performance.
#### Fine-tuning cache sizes
Prior to v5.6.0, the cache sizes were static, and for best performance, many users needed to manually fine-tune the `ram` and `vram` settings in `invokeai.yaml`.
As of v5.6.0, the caches are dynamically sized. The `ram` and `vram` settings are no longer used, and new settings are added to configure the cache.
**Most users will not need to fine-tune the cache sizes.**
But, if your GPU has enough VRAM to hold models fully, you might get a perf boost by manually setting the cache sizes in `invokeai.yaml`:
```yaml
# The default max cache RAM size is logged on InvokeAI startup. It is determined based on your system RAM / VRAM.
# You can override the default value by setting `max_cache_ram_gb`.
# Increasing `max_cache_ram_gb` will increase the amount of RAM used to cache inactive models, resulting in faster model
# reloads for the cached models.
# As an example, if your system has 32GB of RAM and no other heavy processes, setting the `max_cache_ram_gb` to 28GB
# might be a good value to achieve aggressive model caching.
max_cache_ram_gb: 28
# The default max cache VRAM size is adjusted dynamically based on the amount of available VRAM (taking into
# consideration the VRAM used by other processes).
# You can override the default value by setting `max_cache_vram_gb`.
# CAUTION: Most users should not manually set this value. See warning below.
max_cache_vram_gb: 16
```
:::caution[Max safe value for `max_cache_vram_gb`]
Most users should not manually configure the `max_cache_vram_gb`. This configuration value takes precedence over the `device_working_mem_gb` and any operations that explicitly reserve additional working memory (e.g. VAE decode). As such, manually configuring it increases the likelihood of encountering out-of-memory errors.
For users who wish to configure `max_cache_vram_gb`, the max safe value can be determined by subtracting `device_working_mem_gb` from your GPU's VRAM. As described below, the default for `device_working_mem_gb` is 3GB.
For example, if you have a 12GB GPU, the max safe value for `max_cache_vram_gb` is `12GB - 3GB = 9GB`.
If you had increased `device_working_mem_gb` to 4GB, then the max safe value for `max_cache_vram_gb` is `12GB - 4GB = 8GB`.
Most users who override `max_cache_vram_gb` are doing so because they wish to use significantly less VRAM, and should be setting `max_cache_vram_gb` to a value significantly less than the 'max safe value'.
:::
### Working memory
Invoke cannot use _all_ of your VRAM for model caching and loading. It requires some VRAM to use as working memory for various operations.
Invoke reserves 3GB VRAM as working memory by default, which is enough for most use-cases. However, it is possible to fine-tune this setting if you still get OOMs.
#### Fine-tuning working memory
You can increase the working memory size in `invokeai.yaml` to prevent OOMs:
```yaml
# The default is 3GB - bump it up to 4GB to prevent OOMs.
device_working_mem_gb: 4
```
:::tip[Operations may request more working memory]
For some operations, we can determine VRAM requirements in advance and allocate additional working memory to prevent OOMs.
VAE decoding is one such operation. This operation converts the generation process's output into an image. For large image outputs, this might use more than the default working memory size of 3GB.
During this decoding step, Invoke calculates how much VRAM will be required to decode and requests that much VRAM from the model manager. If the amount exceeds the working memory size, the model manager will offload cached model layers from VRAM until there's enough VRAM to decode.
Once decoding completes, the model manager "reclaims" the extra VRAM allocated as working memory for future model loading operations.
:::
### Keeping a RAM weight copy
Invoke has the option of keeping a RAM copy of all model weights, even when they are loaded onto the GPU. This optimization is _on_ by default, and enables faster model switching and LoRA patching. Disabling this feature will reduce the average RAM load while running Invoke (peak RAM likely won't change), at the cost of slower model switching and LoRA patching. If you have limited RAM, you can disable this optimization:
```yaml
# Set to false to reduce the average RAM usage at the cost of slower model switching and LoRA patching.
keep_ram_copy_of_weights: false
```
### Disabling Nvidia sysmem fallback (Windows only)
On Windows, Nvidia GPUs are able to use system RAM when their VRAM fills up via **sysmem fallback**. While it sounds like a good idea on the surface, in practice it causes massive slowdowns during generation.
It is strongly suggested to disable this feature:
- Open the **NVIDIA Control Panel** app.
- Expand **3D Settings** on the left panel.
- Click **Manage 3D Settings** in the left panel.
- Find **CUDA - Sysmem Fallback Policy** in the right panel and set it to **Prefer No Sysmem Fallback**.
![cuda-sysmem-fallback](./assets/cuda-sysmem-fallback.png)
:::tip[Invoke does the same thing, but better]
If the sysmem fallback feature sounds familiar, that's because Invoke's partial model loading strategy is conceptually very similar - use VRAM when there's room, else fall back to RAM.
Unfortunately, the Nvidia implementation is not optimized for applications like Invoke and does more harm than good.
:::
## Troubleshooting
### Windows page file
Invoke has high virtual memory (a.k.a. 'committed memory') requirements. This can cause issues on Windows if the page file size limits are hit. (See this issue for the technical details on why this happens: https://github.com/invoke-ai/InvokeAI/issues/7563).
If you run out of page file space, InvokeAI may crash. Often, these crashes will happen with one of the following errors:
- InvokeAI exits with Windows error code `3221225477`
- InvokeAI crashes without an error, but `eventvwr.msc` reveals an error with code `0xc0000005` (the hex equivalent of `3221225477`)
If you are running out of page file space, try the following solutions:
- Make sure that you have sufficient disk space for the page file to grow. Watch your disk usage as Invoke runs. If it climbs near 100% leading up to the crash, then this is very likely the source of the issue. Clear out some disk space to resolve the issue.
- Make sure that your page file is set to "System managed size" (this is the default) rather than a custom size. Under the "System managed size" policy, the page file will grow dynamically as needed.
@@ -0,0 +1,126 @@
---
title: Patchmatch
---
import { Tabs, TabItem, Steps } from '@astrojs/starlight/components'
PatchMatch is an algorithm used to infill images. It can greatly improve outpainting results. PyPatchMatch is a python wrapper around a C++ implementation of the algorithm.
It uses the image data around the target area as a reference to generate new image data of a similar character and quality.
## Why Use PatchMatch
In the context of image generation, "outpainting" refers to filling in a transparent area using AI-generated image data. But the AI can't generate without some initial data. We need to first fill in the transparent area with _something_.
The first step in "outpainting" then, is to fill in the transparent area with something. Generally, you get better results when that initial infill resembles the rest of the image.
Because PatchMatch generates image data so similar to the rest of the image, it works very well as the first step in outpainting, typically producing better results than other infill methods supported by Invoke (e.g. LaMA, cv2 infill, random tiles).
### Performance Caveat
PatchMatch is CPU-bound, and the amount of time it takes increases proportionally as the infill area increases. While the numbers certainly vary depending on system specs, you can expect a noticeable slowdown once you start infilling areas around 512x512 pixels. 1024x1024 pixels can take several seconds to infill.
## Installation
Unfortunately, installation can be somewhat challenging, as it requires some things that `pip` cannot install for you.
<Steps>
1. Ensure you have the necessary dependencies installed for your system (see below).
<Tabs syncKey="operatingSystem">
<TabItem label="Windows" icon="seti:windows">
You're in luck! On Windows platforms PyPatchMatch will install automatically on Windows systems with no extra intervention.
</TabItem>
<TabItem label="MacOS" icon="apple">
You need to have opencv installed so that pypatchmatch can be built:
```bash
brew install opencv
```
The next time you start `invoke`, after successfully installing opencv, pypatchmatch will be built.
</TabItem>
<TabItem label="Linux" icon="linux">
Prior to installing PyPatchMatch, you need to take the following steps:
<Tabs syncKey="linuxDistro">
<TabItem label="Debian">
<Steps>
1. Install the `build-essential` tools:
```sh
sudo apt update # Update package lists
sudo apt install build-essential
```
2. Install `opencv`:
```sh
sudo apt install python3-opencv libopencv-dev
```
3. Activate the environment you use for invokeai, either with `conda` or with a virtual environment.
</Steps>
</TabItem>
<TabItem label="Arch">
<Steps>
1. Install the `base-devel` package:
```sh
sudo pacman -Syu
sudo pacman -S --needed base-devel
```
2. Install `opencv`, `blas`, and required dependencies:
```sh
sudo pacman -S opencv blas fmt glew vtk hdf5
```
or for CUDA support
```sh
sudo pacman -S opencv-cuda blas fmt glew vtk hdf5
```
3. Fix the naming of the `opencv` package configuration file:
```sh
cd /usr/lib/pkgconfig/
ln -sf opencv4.pc opencv.pc
```
</Steps>
</TabItem>
</Tabs>
</TabItem>
</Tabs>
2. Install pypatchmatch:
```sh
pip install pypatchmatch
```
3. Confirm that pypatchmatch is installed. At the command-line prompt enter `python`, and then at the `>>>` line type `from patchmatch import patch_match`: It should look like the following:
```py
Python 3.12.3 (main, Aug 14 2025, 17:47:21) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from patchmatch import patch_match
Compiling and loading c extensions from "/home/lstein/Projects/InvokeAI/.invokeai-env/src/pypatchmatch/patchmatch".
rm -rf build/obj libpatchmatch.so
mkdir: created directory 'build/obj'
mkdir: created directory 'build/obj/csrc/'
[dep] csrc/masked_image.cpp ...
[dep] csrc/nnf.cpp ...
[dep] csrc/inpaint.cpp ...
[dep] csrc/pyinterface.cpp ...
[CC] csrc/pyinterface.cpp ...
[CC] csrc/inpaint.cpp ...
[CC] csrc/nnf.cpp ...
[CC] csrc/masked_image.cpp ...
[link] libpatchmatch.so ...
```
If you're not seeing any errors, you're ready to go!
</Steps>
@@ -0,0 +1,813 @@
---
title: Call Saved Workflow Architecture
---
## Goal
`CallSavedWorkflowInvocation` should become an engine-native workflow call boundary, not a frontend-only dynamic node
and not a compile-time graph inliner.
The long-term feature goal is:
- A parent workflow can call a saved workflow selected by ID.
- The call node redraws in the editor based on the selected workflow's exposed form fields.
- Parent values and inbound connections bind to those exposed fields as call arguments.
- Execution suspends at the call node, runs the selected workflow as a dependent workflow execution, captures explicit
return values, and then resumes the parent workflow.
- The architecture must work for Invoke frontend graphs and for externally submitted graphs that use the same node type.
This document records the current state, the target architecture, and the execution contract needed to continue
development later.
## Implementation Priority
Favor the architecturally correct design over the fastest implementation path.
The work may still proceed incrementally, but each increment should satisfy all of the following:
- testable in isolation
- compatible with the long-term architecture described here
- non-breaking to existing code and existing workflow execution behavior
Speed is not the primary goal for this phase. The primary goal is to move toward the durable design without introducing
throwaway execution semantics that would need to be unwound later.
## Current State
Implemented already in the branch:
- A real invocation exists: `call_saved_workflow`.
- A real return node exists: `workflow_return`.
- Named returns exist through `workflow_return_value`, `workflow_return`, and caller-side `workflow_return_get`.
- `workflow_return` accepts one key/value return member directly or a collected list of return members, then emits a
named `values: dict[str, Any]` map.
- Only one `workflow_return` node is allowed per workflow, enforced in both frontend validation and Python validation.
- The frontend provides a saved-workflow picker using a reusable `SavedWorkflowField` UI type.
- The node redraws dynamically based on the selected saved workflow's exposed form fields.
- Dynamic field values persist with the parent workflow.
- Compatible inbound edges are preserved when switching between workflows with matching exposed field identities and
compatible types.
- Incompatible or no-longer-exposed inbound edges are removed in the editor.
- Backend validation exists for `workflow_id` existence and access rights.
Implemented runtime scaffolding:
- `GraphExecutionState` now persists workflow-call runtime state:
- `workflow_call_stack`
- `workflow_call_history`
- `workflow_call_parent`
- `waiting_workflow_call`
- `waiting_workflow_call_execution`
- `waiting_workflow_call_child_session`
- `max_workflow_call_depth`
- Nested and recursive calls are represented by the stack, with a runtime depth cap of 4.
- Parent/child workflow-call identity is now explicit in runtime state:
- the parent tracks an active `WorkflowCallExecution` record while waiting
- completed and failed calls are preserved in `workflow_call_history`
- child sessions carry a `workflow_call_parent` reference back to the parent call relationship
- `GraphExecutionState.next()` returns no runnable node while the parent session is waiting on a child workflow call.
- `GraphExecutionState.is_complete()` stays false while waiting.
- `DefaultSessionRunner.run_node()` now treats `call_saved_workflow` as a call boundary instead of a normal executable
node.
- On boundary entry, the runner:
- validates the selected workflow
- builds a workflow call frame
- converts the saved workflow JSON into a backend `Graph`
- validates and applies parent call arguments to the child graph
- creates a child `GraphExecutionState`
- attaches that child session to the waiting parent session
- Workflow-call runtime responsibilities are now split:
- `WorkflowCallCoordinator` handles call-specific setup:
- build the child graph
- apply parent call arguments
- create the child `GraphExecutionState`
- suspend the parent and enqueue the child queue item
- `WorkflowCallQueueLifecycle` handles queue-visible parent/child lifecycle:
- run child queue items
- resume waiting parents after child success
- complete the parent call node with the child `workflow_return` values
- fail suspended parents after child failure and cascade that failure upward through parent call chains
- Child `SessionQueueItem` rows now carry explicit relationship metadata:
- `workflow_call_id`
- `parent_item_id`
- `parent_session_id`
- `root_item_id`
- `workflow_call_depth`
- this metadata is now used directly by queue-visible child execution and parent resume/failure handling
- The `session_queue` table now has matching durable columns for that relationship metadata:
- `workflow_call_id`
- `parent_item_id`
- `parent_session_id`
- `root_item_id`
- `workflow_call_depth`
- child workflow executions are now inserted as their own pending queue rows using those columns
- Parent queue items now enter a real `waiting` status while suspended on a child workflow execution.
- `_on_after_run_session()` no longer completes queue items whose sessions are incomplete but waiting.
- Dynamic call arguments now execute end-to-end in the current runner path:
- literal dynamic values are serialized into a hidden `workflow_inputs` payload on the parent node at graph-build time
- stale hidden `workflow_inputs` values from recalled graphs are ignored unless a matching current dynamic field
exists
- existing dynamic input values are preserved across refresh only while the exposed field type remains compatible; if
the selected child workflow changes the exposed field type at the same node/field path, the caller input resets to
the child workflow's current initial value
- connected dynamic values are accepted as special call-boundary edges and are resolved from parent results at runtime
- both are validated against the child workflow's exposed form interface before being applied to the child graph
- Queue lifecycle semantics now exist for workflow-call chains:
- parent queue items are suspended in `waiting` while a child queue row runs
- child success resumes the suspended parent and completes the parent call node with the child `workflow_return`
values
- child failure fails the suspended parent and cascades upward through any waiting parent chain
- canceling a parent cancels its descendant child chain
- canceling a child cancels the waiting parent chain upward
- canceling remaining siblings after a batched child failure also cancels descendants of those sibling rows
- deleting any queue row in a workflow-call chain deletes the full chain to avoid leaving orphaned parent or child
rows behind
- `cancel_all_except_current` and `delete_all_except_current` preserve the active queue item plus its workflow-call
ancestors and descendants; this also covers the handoff window where a parent is `waiting` and its child is still
`pending`; unrelated waiting chains are still canceled or deleted
- retry is root-oriented rather than child-oriented; child queue rows should not be directly retried from the UI
- the current UI policy is:
- child queue rows keep `Cancel`
- child queue rows hide `Retry`
- child queue-row creation is now fail-clean:
- if call-boundary setup fails after some child rows have already been inserted, those child rows are deleted before
the parent invocation is failed
- child queue-row fan-out is bounded by remaining queue capacity, not just the global queue-size setting:
- a workflow call that would exceed the remaining pending capacity now fails instead of silently truncating or
over-enqueuing child rows
- child insertion rechecks pending capacity in the same database transaction as the insert
Implemented conversion helper:
- `workflow_graph_builder.py` converts saved workflow JSON into an executable backend `Graph`.
- It currently supports the invocation-node subset needed for this feature.
- It flattens connector nodes and omits explicit destination field values when a connection exists, matching frontend
graph-build semantics.
- It now serves as the first explicit callable-workflow compatibility gate:
- the selected workflow must contain exactly one `workflow_return` node
- connected batch child inputs produced by ordinary non-generator upstream nodes still fail early with a clear
unsupported-feature error
- malformed batch input wiring, including multiple connected inputs to one batch field, is reported as
`unsupported_batch_input` compatibility rather than a generic unsupported-node failure
- child workflows that mix supported batch nodes with unrelated generator nodes are currently rejected with a clear
unsupported-feature error
- unsupported callees are rejected before any child queue row is created
- Compatibility metadata is now exposed through workflow library API responses:
- workflow list items and workflow detail responses include `call_saved_workflow_compatibility`
- workflow list items use structural generator-backed batch checks so list/picker rendering does not enumerate every
image in board-backed generators; workflow detail and runtime execution still resolve real generator values
- the saved-workflow picker uses that metadata to disable unsupported workflows before execution
- the picker still allows an already-selected unsupported workflow to render, with an explicit unsupported state and
a localized frontend message selected from the structured backend reason
- workflow library list items now surface an explicit unsupported badge and localized reason without blocking normal
workflow viewing or editing
What is still not implemented:
- connected batch child inputs whose batch values are produced by ordinary non-generator upstream nodes are still not
supported and must fail with a clear domain error
- child workflows that mix supported batch nodes with unrelated generator nodes are still not supported and must fail
with a clear domain error
- broader child-workflow compatibility coverage still needs to be expanded from real unsupported shapes rather than
trying to interpret every frontend-only workflow representation through the current graph-builder path
- the current workflow-call queue lifecycle is still implemented through dedicated workflow-call runtime classes rather
than a fully generalized parent/child scheduler model
Conclusion:
- the editor contract is largely in place
- the parent-side runtime call boundary is in place
- child execution, argument forwarding, explicit child return capture, suspended parent status, queue-visible child
rows, and upward failure cascade now work
- the remaining major runtime work is to harden and generalize the parent/child scheduler model rather than prove the
basic call boundary
## Architectural Direction
Use the architecture that is more likely to be kept long-term:
- `call_saved_workflow` is a call boundary.
- The parent graph does not inline the full child workflow into itself at queue time.
- Runtime execution pauses at the call node and creates a dependent child workflow execution.
- The child workflow receives arguments from the parent.
- The child workflow returns explicit outputs to the parent.
- The parent resumes once the child returns successfully.
This is preferred over full graph expansion because it:
- avoids execution-graph blowup
- preserves workflow boundaries
- matches the conceptual model of workflow reuse
- supports explicit return values
- keeps externally submitted graphs viable as long as they use the same node type and contract
## Non-Goals For The Next Phase
These should not be the first implementation target:
- full inline graph expansion of called workflows
- unlimited nested workflow call support
- automatic exposure of arbitrary internal child workflow state
- implicit output inference from arbitrary child nodes
## Execution Contract
### 1. Callable Interface
The callable interface of a saved workflow is defined by its saved workflow JSON.
Primary source:
- `workflow.form`
Fallback source for older workflows:
- `workflow.exposedFields`
Only fields exposed by the child workflow form are callable inputs. Internal child inputs that exist in the workflow
graph but are not exposed by the form are not part of the public call interface.
### 2. Input Arguments
`CallSavedWorkflowInvocation` exposes dynamic inputs in the editor based on the selected workflow's callable interface.
The saved-workflow picker sends typed search text to the workflow-list endpoint. This keeps large workflow libraries
discoverable even when the desired workflow has not already been loaded into the combobox pages.
Each dynamic input must have:
- a stable external handle name
- a type
- a default value if defined by the child workflow
- a user-facing label and description when available
Current fast-path identity is based on child `nodeId + fieldName`. That is acceptable short-term in the editor, but a
longer-term stable interface ID would be better if child workflows are frequently duplicated or refactored.
### 3. Input Binding At Runtime
At runtime, when the parent reaches `call_saved_workflow`:
- the engine resolves `workflow_id`
- the engine loads the selected child workflow record
- the engine reconstructs the callable interface from the saved workflow JSON
- the engine collects argument values from the parent node's dynamic inputs
- the engine starts a dependent child workflow execution using those arguments
Argument values may come from:
- parent literal field values
- resolved inbound connections into the call node's dynamic inputs
For batch-aware child workflows, the parent call boundary should still pass normal exposed form inputs. Batching should
emerge from the child workflow's own internal batch nodes or generators, not from a separate caller-side batch protocol.
### 4. Child Workflow Execution
The child workflow runs as its own dependent execution context, not as an inlined copy of the parent graph.
Desired semantics:
- parent execution pauses at the call node
- child execution runs with inherited context where appropriate
- child workflow finishes or fails
- parent resumes only if child execution succeeds
This implies the queue/session/runtime layer needs an explicit parent-child execution relationship.
Current limitation:
- the temporary `workflow_graph_builder.py` path still reconstructs only the ordinary invocation subset of child
workflows
- direct batch-special child workflows now bypass that path and use queue batch expansion instead
- generator-backed batch child workflows now bypass that path too when the batch is fed directly by a supported
generator node
- connected batch child inputs produced by ordinary non-generator upstream nodes are still not supported and should fail
early with a clear unsupported-feature error
- the current queue-visible child execution path still relies on `WorkflowCallCoordinator` to resume or fail parents
directly rather than a more general queue scheduler abstraction
- the current implementation is still an intermediate architecture step, but it is now materially closer to the intended
durable parent/child model than the earlier inline-runner path
### 4a. Queue Lifecycle Contract
The current queue-visible implementation uses the following lifecycle contract:
- root or parent queue items may enter `waiting` while suspended on a child workflow call
- child workflow executions are represented as real queue rows with explicit parent/child relationship metadata
- child completion resumes the suspended parent and returns control to normal queue execution
- child failure fails the suspended parent call node and cascades upward through any ancestor chain
- cancel operations are chain-aware:
- canceling a waiting parent cancels descendants
- canceling a child cancels waiting ancestors
- canceling batched siblings after one child fails includes nested descendants of those siblings
- bulk "all except current" actions preserve the active queue item and its parent/child chain, not just the single
`in_progress` row; a pending child with a waiting parent is treated as the active chain during processor handoff
- retry operations are root-aware:
- retrying a root queue item creates a new root execution
- retrying a child queue item should be normalized to the root by backend code
- retry and full-chain delete authorization is checked against the root queue item owner
- child queue rows should not expose direct retry affordances in the UI
- retry websocket delivery is owner-scoped; when an admin retries roots owned by multiple users, each non-admin user
must receive only the retry item ids for their own roots, while admins can still observe the full retried set
- workflow live-update sockets join workflow event rooms in both authenticated multiuser mode and unauthenticated
single-user mode; the frontend relies on those events to invalidate workflow library data and clear deleted saved
workflow selections; in single-user mode, workflow CRUD events emit only to the admin room to avoid duplicate delivery
to sockets that are also joined to `user:system`
- a public-to-private transition emits a schema-defined `workflow_access_revoked` event to shared-workflow subscribers;
non-owner, non-admin clients clear references to that workflow while owners and admins retain access
- the saved-workflow node picker queries owned/default workflows and public shared workflows separately, merges them by
workflow id, and fetches additional pages as the combobox menu reaches the end
- queue status events must preserve user isolation:
- `QueueItemStatusChangedEvent.queue_status` may keep global aggregate counts
- embedded current-item identifiers (`item_id`, `session_id`, `batch_id`) must only be present when the current
in-progress item belongs to the event owner, or when the status is being built for an admin/global context
- workflow-call child enqueue events use the same owner-aware redaction as ordinary status transitions, even though
they do not pass through `_set_queue_item_status`
This is now part of the intended user-facing contract, even though the orchestration still lives in
`WorkflowCallCoordinator`.
### 4b. Batch Child Workflows
The current implementation now supports direct batch-special child workflows for:
- `image_batch`
- `string_batch`
- `integer_batch`
- `float_batch`
It also supports generator-backed batch child workflows when those batch nodes are fed directly by:
- `integer_generator`
- `float_generator`
- `string_generator`
- `image_generator` using `image_generator_images_from_board`
Current semantics:
- batch-special nodes are removed from the executable child graph before ordinary graph validation
- supported generator nodes that feed those batch-special nodes are removed from the executable child graph as well
- their outgoing edges are converted into queue batch substitutions
- ungrouped batch nodes expand as a cartesian product
- grouped batch nodes zip by `batch_group_id`
- the workflow call creates one child queue row per expanded batch session
- supported generator value shapes are resolved into concrete batch items before queue batch expansion
- declared generator counts are rejected before resolution when they exceed remaining child capacity
- cartesian expansion size is computed arithmetically before session generation rather than by materializing the product
- batch outputs may feed a named `workflow_return_value.value` directly; each expanded child returns one value for that
key
- parent resume waits for all child rows tied to that workflow call
- parent return aggregation produces `values: dict[str, list[Any]]`, where each key maps to one value per child row
- all child rows in one batch call must return the same key set; mismatched keys fail the parent call clearly
- if any child row fails, remaining sibling child rows are canceled and the parent call fails
- generator-backed image batches must respect board access:
- the caller may expand images from a board they own
- admins may expand any board
- shared/public boards may be expanded by other users
- inaccessible private boards must fail before image expansion rather than leaking board contents across users
Current generator coverage:
- integer generators:
- arithmetic sequence
- linear distribution
- parse string
- seeded uniform random distribution
- float generators:
- arithmetic sequence
- linear distribution
- parse string
- seeded uniform random distribution
- string generators:
- parse string
- dynamic prompts combinatorial
- dynamic prompts random
- image generators:
- images from board
Still unsupported:
- connected batch inputs whose batch values are produced by non-generator upstream nodes
Plain-English summary:
1. The parent workflow reaches `call_saved_workflow`.
1. The parent pauses and enters `waiting`.
1. The child workflow is inspected before execution.
1. If the child contains supported batch inputs, that one call expands into multiple child executions instead of one.
1. Each expanded child execution becomes its own queue row.
1. Each child queue row keeps the substituted batch `field_values`, matching ordinary batch queue rows.
1. Those child queue rows run independently.
1. The parent does not resume until all child queue rows for that call have finished.
1. Each child execution produces its own named `workflow_return.values` map.
1. The parent aggregates those maps into `values: dict[str, list[Any]]`.
1. The `call_saved_workflow` node completes with that named values map, and the parent workflow continues.
Expansion rules:
- ungrouped batch inputs expand as a cartesian product
- batch inputs that share the same `batch_group_id` zip together by position
Example:
- ungrouped inputs `[1, 2]` and `[10, 20]` produce 4 child executions:
- `(1, 10)`
- `(1, 20)`
- `(2, 10)`
- `(2, 20)`
- grouped inputs `[1, 2, 3]` and `[10, 20, 30]` with the same `batch_group_id` produce 3 child executions:
- `(1, 10)`
- `(2, 20)`
- `(3, 30)`
### 4c. Tricky Areas
The following parts of the runtime contract are easy to misread and should stay explicit in both code and tests.
Waiting and resume:
- a parent queue row in `waiting` is suspended, not completed
- a parent resumes only after every child queue row tied to that workflow call has reached a terminal state
Return aggregation:
- each child queue row returns its own named `workflow_return.values`
- for batched calls, the parent call node output is `values: dict[str, list[Any]]`
- all child rows in one batched call must return the same key set so each returned list is row-aligned
- if one key should contain multiple images for a non-batch child, the child must collect those images into a single
list value before returning that key
Sibling failure behavior:
- if one child queue row in a batched workflow call fails, remaining sibling child rows for that same workflow call are
canceled
- if parent return aggregation rejects a completed child row, remaining sibling child rows for that same workflow call
are canceled
- after sibling cancelation, the parent call fails
- if that parent is itself a child of another workflow call, failure continues upward through the ancestor chain
Cancel behavior:
- canceling a waiting parent cancels descendant child rows
- canceling a child row cancels waiting ancestors
- cancelation should stay cancelation; it should not be rewritten into ordinary failure semantics
- startup recovery cancels any interrupted `in_progress` or `waiting` workflow-call chain, including pending
descendants, so a restart cannot leave a suspended parent waiting on a child row that will never report back
Retry behavior:
- retry is root-oriented
- child queue rows should not be directly retried from the UI
- backend retry of a child id should normalize to the root workflow call chain rather than create an isolated child-only
rerun
### 5. Return Values
Return values should be explicit.
Implemented model:
- `workflow_return` is the explicit return node for callable workflows
- the child workflow declares named return values through explicit `workflow_return_value` key/value return members
- each return member has a stable string key and a connected value
- when the workflow is run independently, the return node has no caller-visible effect
- when the workflow is run via `call_saved_workflow`, the named return map becomes the return value of the call
- `call_saved_workflow` exposes that named return map to the parent workflow
Only one workflow return node may exist per workflow. That rule is enforced in both the frontend editor and in Python
validation/runtime code.
Do not infer child outputs from arbitrary terminal nodes. That is too ambiguous and too brittle.
Named return contract:
- the called workflow builds return members with a dedicated key/value node
- `workflow_return` accepts one return member directly, or a collected list of return members when the workflow returns
multiple named values
- non-batch execution rejects duplicate return keys
- if a non-batch workflow needs to return multiple images under one key, the child workflow should collect those images
into one list value and return that list under the key
- the caller extracts a named return value with a companion caller-side extraction node
- missing keys should fail clearly unless the extraction node explicitly supports and receives a default value
Batch return aggregation:
- when a called workflow expands into multiple child queue rows, each child row produces its own named return map
- the parent aggregates those child maps as `dict[str, list[Any]]`
- each key maps to child values in child enqueue order, preserving positional correspondence with batch inputs even when
child executions complete out of order
- duplicate keys within a single child return map are still invalid; repeated keys across batch children are the normal
aggregation path
### 6. Error Propagation
If child execution fails:
- the call node fails
- the parent workflow fails unless a later design adds explicit error-handling semantics
For the first implementation, failure propagation should be simple and strict.
### 7. Access Control
Runtime must enforce the same access rules used elsewhere for saved workflows.
The caller may execute a child workflow only if it is allowed to access that saved workflow at runtime.
This matters even if the parent workflow was authored in a context where the child was once visible.
### 8. Recursion And Nesting
Nested and recursive `call_saved_workflow` execution should be allowed, but bounded.
Initial implementation should enforce:
- nested workflow calls are allowed
- recursive workflow calls are allowed
- maximum workflow call depth is capped at 4 call frames
- the depth cap is enforced at runtime, based on the active call stack, not by static validation alone
This allows legitimate recursive or conditionally terminating workflow structures while still preventing unbounded call
growth.
## Where The Runtime Work Belongs
The goal is to support externally submitted graphs, not only frontend-authored graphs. Therefore the authoritative
execution logic must live in Python.
Recommended high-level design:
- a backend `GraphExpander` or broader graph-preparation service may still exist as an abstraction point
- but for this feature, the preferred long-term runtime model is not full graph expansion
- instead, the runtime needs a call-execution mechanism in the Python execution stack
Relevant existing path:
- frontend builds and submits a graph and workflow payload
- backend receives the batch via session queue APIs
- session queue stores session state
- runtime executes through `GraphExecutionState`
Current insertion points already used:
- `DefaultSessionRunner.run_node()` detects `call_saved_workflow` and enters boundary state
- `GraphExecutionState` stores the waiting/call-stack state and attached child session
- `WorkflowCallCoordinator` currently establishes the call boundary and enqueues child workflow executions as real queue
rows
- `WorkflowCallQueueLifecycle` currently resumes or fails parents when those child rows complete
- child queue items already carry stable parent/child identifiers in both runtime objects and durable queue columns
Next runtime work still needed:
- keep `WorkflowCallQueueLifecycle` as the bounded workflow-call lifecycle component for this PR
- the current workflow-call feature is the only caller of parent/child queue semantics
- replacing it with a generalized queue dependency scheduler now would add regression risk without unlocking a
concrete user workflow
- revisit only if another feature needs dependent queue items, richer retry/cancel policies, or resumable waits
- if support expands beyond the currently supported direct and generator-backed batch shapes, route those new child
workflow execution shapes through machinery that can honor ordinary Invoke batch semantics
## Runtime Components
### CallSavedWorkflowRuntime
Call-specific runtime behavior currently lives in `WorkflowCallCoordinator` and `WorkflowCallQueueLifecycle`.
Responsibilities:
- load and validate the selected child workflow record
- validate runtime access rights
- extract callable inputs from the child workflow definition
- build child execution arguments from the parent node state
- launch dependent child queue rows
- collect declared returns
- map returned values back to the parent node outputs
### Workflow Return Node
The dedicated child-workflow return nodes are implemented. Responsibilities:
- define the return interface of the called workflow
- build named key/value return members with `workflow_return_value`
- accept either one named return member or a collected list of return members with `workflow_return`
- provide that named values map back to the parent call site when invoked through `call_saved_workflow`
- remain inert from a caller perspective when the workflow is run independently
- guarantee that only one such node exists per workflow
- behave as a normal node in the editor, with singularity enforced by both frontend and Python validation/runtime code
This should remain the canonical reusable return mechanism for any future subworkflow call behavior.
### Execution Relationship Tracking
Session/runtime state records:
- parent execution waiting on child execution
- child execution belonging to a parent node call site
- result propagation back to the parent
- strict failure propagation rules
### Workflow Return Value Flow
The workflow return value should not be persisted back into the saved workflow record and should not be derived from
frontend state.
The intended runtime flow is:
1. The child workflow computes named return members like ordinary node outputs.
1. The child workflow connects one return member directly to `workflow_return.values`, or collects multiple return
members and connects that list to `workflow_return.values`.
1. When the child reaches `workflow_return`, runtime captures the resolved named return map as the child workflow
result.
1. The child workflow result is stored in child execution state.
1. That result is handed back to the suspended parent call frame.
1. The parent `call_saved_workflow` node is completed with that returned named value map.
1. The parent graph resumes.
## Named Return Implementation Status
Named returns are implemented for backend invocation behavior, caller-side extraction, runtime propagation, and batch
aggregation. Remaining work is limited to incremental frontend UX cleanup and any future expansion of supported batch
shapes.
### Stage 1: Backend Return Contract
Status: implemented in backend invocation tests.
Goal:
- establish the named return data model and invocation primitives
Contract:
- `WorkflowReturnValueField` stores one `key: str` and one `value: Any`
- `workflow_return_value` creates a single `WorkflowReturnValueField` from a key and connected value
- `workflow_return` accepts either one `WorkflowReturnValueField` member or a list of `WorkflowReturnValueField` members
- `WorkflowReturnOutput` exposes `values: dict[str, Any]`
- duplicate keys in one non-batch `workflow_return` execution are invalid and must fail clearly
Tests first:
- `workflow_return_value` emits the requested key/value pair
- `workflow_return` emits a named value map from one or more return members
- duplicate keys in one `workflow_return` execution are rejected
- empty returns are valid only if that remains an intentional callable-workflow contract
### Stage 2: Caller-Side Extraction Primitive
Status: implemented in backend invocation tests.
Goal:
- let the calling workflow extract a named return value without relying on collection position
Contract:
- `workflow_return_get` accepts the named return map and a key
- `workflow_return_get` outputs the selected value as `Any`
- missing keys fail clearly unless a later version intentionally adds default-value support
Tests first:
- extracting an existing key returns the stored value
- extracting a missing key fails with a useful message
- extracted `Any` values can feed typed downstream nodes through the existing connection compatibility rules
### Stage 3: Runtime Propagation
Status: implemented in backend runtime tests.
Goal:
- carry named return maps through queue-visible child execution and parent resume
Contract:
- non-batch child execution returns `values: dict[str, Any]`
- `call_saved_workflow` exposes that map on its output
- failed child execution behavior is unchanged
- cancel/retry lifecycle behavior is unchanged
Tests first:
- a called workflow returning `{image: image_value}` completes the parent `call_saved_workflow` output with that key
- a caller-side extraction node can consume that output after parent resume
- missing or invalid `workflow_return` nodes still fail with the existing clear errors
### Stage 4: Batch Return Aggregation
Status: implemented in backend runtime tests.
Goal:
- define named returns for child workflows that expand into multiple queue rows
Contract:
- each child queue row produces one named return map
- the parent aggregates child maps as `dict[str, list[Any]]`
- each key maps to values returned by completed child rows for that key
- all child rows in one batch call must return the same key set
- repeated keys across child rows are expected
- duplicate keys within one child row remain invalid
- if a non-batch workflow wants multiple images under one key, it must collect those images into a single list value
before returning that key
Tests first:
- a batched child returning `{image: image_value}` from each child row produces `{image: [image_1, image_2, ...]}`
- sibling failure still cancels remaining siblings and fails the parent
- duplicate keys inside one child row are rejected rather than silently aggregated
### Stage 5: Frontend Schema, UI, And Docs
Status: mostly implemented. Schema/type generation includes the backend nodes and fields; editor connection coverage and
localized UI strings are in place for the current node wiring. Future UX cleanup should be driven by concrete user
testing rather than added as speculative work.
Goal:
- make named returns usable and visible in the editor
Contract:
- generated schema/types include the new return field, return-value node, and extraction node
- visible UI strings are localized through `en.json`
- `call_saved_workflow` exposes the named return map output
- users can wire that output to `workflow_return_get`
Tests first:
- frontend connection/type tests cover return-value collection wiring
- frontend connection/type tests cover wiring one `workflow_return_value.value` directly to `workflow_return.values`
- frontend connection/type tests cover `call_saved_workflow.values -> workflow_return_get.values`
- docs describe how a called workflow creates named returns and how a caller extracts them
## Frontend Responsibilities In The Long-Term Design
The frontend remains responsible for editor-time behavior:
- choosing the saved workflow
- redrawing dynamic inputs based on the child workflow callable interface
- persisting those dynamic fields and their values
- preserving compatible inbound edges when workflow selection changes
- clearing incompatible edges and invalid selections in a predictable way
- using backend compatibility metadata so unsupported saved workflows are not presented as callable choices
- compatibility analysis now tolerates required exposed caller inputs by synthesizing placeholder values for those
inputs during backend compatibility evaluation, so workflows that are valid once the caller supplies exposed values
are not disabled prematurely
Potential future optimization:
- add a backend endpoint that returns a normalized callable workflow interface
- this would let the frontend avoid re-parsing full saved workflow payloads to redraw the node
- it would also give the frontend a backend-authoritative interface hash for drift detection
## Tests Needed Going Forward
Already covered:
- workflow-call stack and waiting state on `GraphExecutionState`
- depth-limit enforcement
- waiting blocks scheduling
- parent sessions are not completed while waiting
- runner boundary entry for `call_saved_workflow`
- validation failures and depth-limit failures still follow normal node-error behavior
- child workflow JSON conversion to backend `Graph`
- child graph build failure does not leave the parent in a partial waiting state
- child `GraphExecutionState` is attached to the waiting parent session
- coordinator-owned child execution completes the parent queue item instead of leaving it stuck in `in_progress`
- literal and connected dynamic call arguments are applied to the child graph at runtime
- non-exposed dynamic call arguments are rejected at runtime
- child `workflow_return` output is captured and becomes the parent `call_saved_workflow` output
- named `workflow_return` values can be constructed, propagated to the parent, extracted by key, and batch-aggregated as
`dict[str, list[Any]]`
- child workflows without a `workflow_return` node fail cleanly when called
- child execution events now include stable workflow-call relationship metadata on the child `SessionQueueItem`
- parent-child resume and failure propagation through queue-visible child rows
- nested runtime execution with bounded stack depth
- direct and generator-backed batch-special child workflows through queue child-row expansion
- compatibility metadata for required exposed inputs, missing/multiple returns, supported named batch-return shapes, and
unsupported batch input wiring
Still needed in later increments:
- focused coverage for any newly supported batch or generator shape when its contract changes
- possible migration from dedicated workflow-call queue lifecycle handling to a more general scheduler or
queue-lifecycle model only if another feature needs reusable dependent queue items
## Recommended Immediate Next Step
The next incremental step should be:
- stop adding feature slices unless they close a concrete correctness gap or unlock a realistic user workflow
- stabilize the current branch with review, targeted test runs, and cleanup of stale design-doc language
- treat migration from `WorkflowCallQueueLifecycle` to a generalized parent/child queue lifecycle as a larger
architecture slice, not as small follow-on busywork
The current branch is at the point where:
- parent call-boundary state exists
- child execution state can be created from the selected saved workflow
- child execution, argument forwarding, explicit return propagation, suspended parent status, queue-visible child rows,
and upward failure cascade work through the current coordinator + queue path
- but long-term generalized parent/child scheduling semantics are still missing
@@ -0,0 +1,130 @@
---
title: Code of Conduct
---
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported to the community leaders responsible for enforcement
at https://github.com/invoke-ai/InvokeAI/issues. All complaints will
be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
@@ -0,0 +1,54 @@
---
title: Contributors
---
We thank [all contributors](https://github.com/invoke-ai/InvokeAI/graphs/contributors) for their time and hard work!
## Original Author
- [Lincoln D. Stein](mailto:lincoln.stein@gmail.com)
## Current Core Team
- [@lstein](https://github.com/lstein) (Lincoln Stein) - Co-maintainer
- [@blessedcoolant](https://github.com/blessedcoolant) - Co-maintainer
- [@hipsterusername](https://github.com/hipsterusername) (Kent Keirsey) - Co-maintainer, CEO, Positive Vibes
- [@psychedelicious](https://github.com/psychedelicious) (Spencer Mabrito) - Web Team Leader
- [@joshistoast](https://github.com/joshistoast) (Josh Corbett) - Web Development
- [@cheerio](https://github.com/cheerio) (Mary Rogers) - Lead Engineer & Web App Development
- [@ebr](https://github.com/ebr) (Eugene Brodsky) - Cloud/DevOps/Software engineer; your friendly neighbourhood cluster-autoscaler
- [@sunija](https://github.com/sunija) - Standalone version
- [@brandon](https://github.com/brandon) (Brandon Rising) - Platform, Infrastructure, Backend Systems
- [@ryanjdick](https://github.com/ryanjdick) (Ryan Dick) - Machine Learning & Training
- [@JPPhoto](https://github.com/JPPhoto) - Core image generation nodes
- [@dunkeroni](https://github.com/dunkeroni) - Image generation backend
- [@SkunkWorxDark](https://github.com/SkunkWorxDark) - Image generation backend
- [@glimmerleaf](https://github.com/glimmerleaf) (Devon Hopkins) - Community Wizard
- [@gogurt](https://github.com/gogurt) enjoyer - Discord moderator and end user support
- [@whosawhatsis](https://github.com/whosawhatsis) - Discord moderator and end user support
- [@dwringer](https://github.com/dwringer) - Discord moderator and end user support
- [@526christian](https://github.com/526christian) - Discord moderator and end user support
- [@harvester62](https://github.com/harvester62) - Discord moderator and end user support
## Honored Team Alumni
- [@StAlKeR7779](https://github.com/StAlKeR7779) (Sergey Borisov) - Torch stack, ONNX, model management, optimization
- [@damian0815](https://github.com/damian0815) - Attention Systems and Compel Maintainer
- [@netsvetaev](https://github.com/netsvetaev) (Artur) - Localization support
- [@Kyle0654](https://github.com/Kyle0654) (Kyle Schouviller) - Node Architect and General Backend Wizard
- [@tildebyte](https://github.com/tildebyte) - Installation and configuration
- [@mauwii](https://github.com/mauwii) (Matthias Wilde) - Installation, release, continuous integration
- [@chainchompa](https://github.com/chainchompa) (Jennifer Player) - Web Development & Chain-Chomping
- [@millu](https://github.com/millu) (Millun Atluri) - Community Wizard, Documentation, Node-wrangler,
- [@genomancer](https://github.com/genomancer) (Gregg Helt) - Controlnet support
- [@keturn](https://github.com/keturn) (Kevin Turner) - Diffusers
## Original CompVis (Stable Diffusion) Authors
- [Robin Rombach](https://github.com/rromb)
- [Patrick von Platen](https://github.com/patrickvonplaten)
- [ablattmann](https://github.com/ablattmann)
- [Patrick Esser](https://github.com/pesser)
- [owenvincent](https://github.com/owenvincent)
- [apolinario](https://github.com/apolinario)
- [Charles Packer](https://github.com/cpacker)
@@ -0,0 +1,131 @@
---
title: External Provider Integration
---
This guide covers:
1. Adding a new **external model** (most common; existing provider).
2. Adding a brand-new **external provider** (adapter + config + UI wiring).
## 1) Add a New External Model (Existing Provider)
For provider-backed models (for example, OpenAI or Gemini), the source of truth is
`invokeai/backend/model_manager/starter_models.py`.
### Required model fields
Define a `StarterModel` with:
- `base=BaseModelType.External`
- `type=ModelType.ExternalImageGenerator`
- `format=ModelFormat.ExternalApi`
- `source="external://<provider_id>/<provider_model_id>"`
- `name`, `description`
- `capabilities=ExternalModelCapabilities(...)`
- optional `default_settings=ExternalApiModelDefaultSettings(...)`
Example:
```python
new_external_model = StarterModel(
name="Provider Model Name",
base=BaseModelType.External,
source="external://openai/my-model-id",
description=(
"Provider model (external API). "
"Requires a configured OpenAI API key and may incur provider usage costs."
),
type=ModelType.ExternalImageGenerator,
format=ModelFormat.ExternalApi,
capabilities=ExternalModelCapabilities(
modes=["txt2img", "img2img", "inpaint"],
supports_negative_prompt=False,
supports_seed=False,
supports_guidance=False,
supports_steps=False,
supports_reference_images=True,
max_images_per_request=4,
),
default_settings=ExternalApiModelDefaultSettings(
width=1024,
height=1024,
num_images=1,
),
)
```
Then append it to `STARTER_MODELS`.
### Required description text
External starter model descriptions must clearly state:
- an API key is required
- usage may incur provider-side costs
### Capabilities must be accurate
These flags directly control UI visibility and request payload fields:
- `supports_negative_prompt`
- `supports_seed`
- `supports_guidance`
- `supports_steps`
- `supports_reference_images`
`supports_steps` is especially important: if `False`, steps are hidden for that model and `steps` is sent as `null`.
### Source string stability
Starter overrides are matched by `source` (`external://provider/model-id`). Keep this stable:
- runtime capability/default overrides depend on it
- installation detection in starter-model APIs depends on it
`STARTER_MODELS` enforces unique `source` values with an assertion.
### Install behavior notes
- External starter models are managed in **External Providers** setup (not the regular Starter Models tab).
- External starter models auto-install when a provider is configured.
- Removing a provider API key removes installed external models for that provider.
## 2) Credentials and Config
External provider API keys are stored separately from `invokeai.yaml`:
- default file: `~/invokeai/api_keys.yaml`
- resolved path: `<INVOKEAI_ROOT>/api_keys.yaml`
Non-secret provider settings (for example base URL overrides) stay in `invokeai.yaml`.
Environment variables are still supported, e.g.:
- `INVOKEAI_EXTERNAL_GEMINI_API_KEY`
- `INVOKEAI_EXTERNAL_OPENAI_API_KEY`
## 3) Add a New Provider (Only If Needed)
If your model uses a provider that is not already integrated:
1. Add config fields in `invokeai/app/services/config/config_default.py`
`external_<provider>_api_key` and optional `external_<provider>_base_url`.
2. Add provider field mapping in `invokeai/app/api/routers/app_info.py`
(`EXTERNAL_PROVIDER_FIELDS`).
3. Implement provider adapter in `invokeai/app/services/external_generation/providers/`
by subclassing `ExternalProvider`.
4. Register the provider in `invokeai/app/api/dependencies.py` when building
`ExternalGenerationService`.
5. Add starter model entries using `source="external://<provider>/<model-id>"`.
6. Optional UI ordering tweak:
`invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ExternalProviders/ExternalProvidersForm.tsx`
(`PROVIDER_SORT_ORDER`).
## 4) Optional Manual Installation
You can also install external models directly via:
`POST /api/v2/models/install?source=external://<provider_id>/<provider_model_id>`
If omitted, `path`, `source`, and `hash` are auto-populated for external model configs.
Set capabilities conservatively; the external generation service enforces capability checks at runtime.
@@ -0,0 +1,56 @@
---
title: Contributing to InvokeAI
sidebar:
order: 1
---
Invoke originated as a project built by the community, and that vision carries forward today as we aim to build the best pro-grade tools available. We work together to incorporate the latest in AI/ML research, making these tools available in over 20 languages to artists and creatives around the world as part of our fully permissive OSS project designed for individual users to self-host and use.
We welcome contributions, whether features, bug fixes, code cleanup, testing, code reviews, documentation or translation. Please check in with us before diving in to code to ensure your work aligns with our vision.
## Development
If youd like to help with development, please see our [development guide](/development/).
**New Contributors:** If youre unfamiliar with contributing to open source projects, take a look at our [new contributor guide](/contributing/new-contributor-guide/).
## Nodes
If youd like to add a Node, please see our [nodes contribution guide](/development/guides/creating-nodes/).
## Support and Triaging
Helping support other users in [Discord](https://discord.gg/ZmtBAhwWhy) and on Github are valuable forms of contribution that we greatly appreciate.
We receive many issues and requests for help from users. We're limited in bandwidth relative to our the user base, so providing answers to questions or helping identify causes of issues is very helpful. By doing this, you enable us to spend time on the highest priority work.
## Documentation
If youd like to help with documentation, please see our [contributing guide](/contributing/).
## Translation
If you'd like to help with translation, please see our [translation guide](/contributing/translations/).
## Tutorials
Please reach out to @hipsterusername on [Discord](https://discord.gg/ZmtBAhwWhy) to help create tutorials for InvokeAI.
## Contributors
This project is a combined effort of dedicated people from across the world. [Check out the list of all these amazing people](/contributing/contributors/). We thank them for their time, hard work and effort.
## Code of Conduct
The InvokeAI community is a welcoming place, and we want your help in maintaining that. Please review our [Code of Conduct](/contributing/code-of-conduct/) to learn more - it's essential to maintaining a respectful and inclusive environment.
By making a contribution to this project, you certify that:
1. The contribution was created in whole or in part by you and you have the right to submit it under the open-source license indicated in this projects GitHub repository; or
2. The contribution is based upon previous work that, to the best of your knowledge, is covered under an appropriate open-source license and you have the right under that license to submit that work with modifications, whether created in whole or in part by you, under the same open-source license (unless you are permitted to submit under a different license); or
3. The contribution was provided directly to you by some other person who certified (1) or (2) and you have not modified it; or
4. You understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information you submit with it, including your sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open-source license(s) involved.
This disclaimer is not a license and does not grant any rights or permissions. You must obtain necessary permissions and licenses, including from third parties, before contributing to this project.
This disclaimer is provided "as is" without warranty of any kind, whether expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, or non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the contribution or the use or other dealings in the contribution.
@@ -0,0 +1,105 @@
---
title: New Contributor Guide
lastUpdated: 2026-02-19
---
import { Steps, LinkCard } from '@astrojs/starlight/components';
If you're a new contributor to InvokeAI or Open Source Projects, this is the guide for you.
## New Contributor Checklist
<Steps>
1. Set up your local development environment & fork of InvokAI by following [the steps outlined here](../../development/setup/dev-environment/#initial-setup)
2. Set up your local tooling with [this guide](/development/). Feel free to skip this step if you already have tooling you're comfortable with.
3. Familiarize yourself with [Git](https://www.atlassian.com/git) & our project structure by reading through the [development documentation](/development/)
4. Join the [#dev-chat](https://discord.com/channels/1020123559063990373/1049495067846524939) channel of the Discord
5. Choose an issue to work on! This can be achieved by asking in the #dev-chat channel, tackling a [good first issue](https://github.com/invoke-ai/InvokeAI/contribute) or finding an item on the [roadmap](https://github.com/orgs/invoke-ai/projects/7). If nothing in any of those places catches your eye, feel free to work on something of interest to you!
6. Make your first Pull Request with the guide below
7. Happy development! Don't be afraid to ask for help - we're happy to help you contribute!
</Steps>
## How do I make a contribution?
Never made an open source contribution before? Wondering how contributions work in our project? Here's a quick rundown!
Before starting these steps, ensure you have your local environment [configured for development](/development/setup/dev-environment/).
<Steps>
1. Find a [good first issue](https://github.com/invoke-ai/InvokeAI/contribute) that you are interested in addressing or a feature that you would like to add. Then, reach out to our team in the [#dev-chat](https://discord.com/channels/1020123559063990373/1049495067846524939) channel of the Discord to ensure you are setup for success.
2. Fork the [InvokeAI](https://github.com/invoke-ai/InvokeAI) repository to your GitHub profile. This means that you will have a copy of the repository under **your-GitHub-username/InvokeAI**.
3. Clone the repository to your local machine using:
```bash
git clone https://github.com/your-GitHub-username/InvokeAI.git
```
If you're unfamiliar with using Git through the commandline, [GitHub Desktop](https://desktop.github.com) is a easy-to-use alternative with a UI. You can do all the same steps listed here, but through the interface. 4. Create a new branch for your fix using:
```bash
git checkout -b branch-name-here
```
5. Make the appropriate changes for the issue you are trying to address or the feature that you want to add.
6. Add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index:
```bash
git add -A
```
7. Store the contents of the index with a descriptive message.
```bash
git commit -m "Insert a short message of the changes made here"
```
8. Push the changes to the remote repository using
```bash
git push origin branch-name-here
```
9. Submit a pull request to the **main** branch of the InvokeAI repository. If you're not sure how to, [follow this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)
10. Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like so "Added more log outputting to resolve #1234".
11. In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainer. It's OK if your pull request is not perfect (no pull request is), the reviewer will be able to help you fix any problems and improve it!
12. Wait for the pull request to be reviewed by other collaborators.
13. Make changes to the pull request if the reviewer(s) recommend them.
14. Celebrate your success after your pull request is merged!
</Steps>
<LinkCard
title="Learn More About Contributing to Open Source Projects"
description="Learn more about how to make a pull request and contribute to open source projects with this guide."
href="https://opensource.com/article/19/7/create-pull-request-github"
/>
:::tip[Best Practices]
- Keep your pull requests small. Smaller pull requests are more likely to be accepted and merged.
- Comments! Commenting your code helps reviewers easily understand your contribution.
- Use Python and Typescripts typing systems, and consider using an editor with [LSP](https://microsoft.github.io/language-server-protocol/) support to streamline development.
- Make all communications public. This ensure knowledge is shared with the whole community.
:::
## **Where can I go for help?**
If you need help, you can ask questions in the [#dev-chat](https://discord.com/channels/1020123559063990373/1049495067846524939) channel of the Discord.
For frontend related work, **@pyschedelicious** is the best person to reach out to.
For backend related work, please reach out to **@blessedcoolant**, **@lstein**, **@StAlKeR7779** or **@pyschedelicious**.
@@ -0,0 +1,21 @@
---
title: Translations
---
InvokeAI uses [Weblate](https://weblate.org/) for translation. Weblate is a FOSS project providing a scalable translation service. Weblate automates the tedious parts of managing translation of a growing project, and the service is generously provided at no cost to FOSS projects like InvokeAI.
## Contributing
If you'd like to contribute by adding or updating a translation, please visit our [Weblate project](https://hosted.weblate.org/engage/invokeai/). You'll need to sign in with your GitHub account (a number of other accounts are supported, including Google).
Once signed in, select a language and then the Web UI component. From here you can Browse and Translate strings from English to your chosen language. Zen mode offers a simpler translation experience.
Your changes will be attributed to you in the automated PR process; you don't need to do anything else.
## Help & Questions
Please check Weblate's [documentation](https://docs.weblate.org/en/latest/index.html) or ping @Harvestor on [Discord](https://discord.com/channels/1020123559063990373/1049495067846524939) if you have any questions.
## Thanks
Thanks to the InvokeAI community for their efforts to translate the project!
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,425 @@
---
title: Invocations
lastUpdated: 2026-02-18
---
import { FileTree, Code, Steps } from '@astrojs/starlight/components'
# Nodes
Features in InvokeAI are added in the form of modular nodes systems called
**Invocations**.
An Invocation is simply a single operation that takes in some inputs and gives
out some outputs. We can then chain multiple Invocations together to create more
complex functionality.
## Invocations Directory
InvokeAI Nodes can be found in the `invokeai/app/invocations` directory. These
can be used as examples to create your own nodes.
New nodes should be added to a subfolder in `nodes` direction found at the root
level of the InvokeAI installation location. Nodes added to this folder will be
able to be used upon application startup.
Example `nodes` subfolder structure:
<FileTree>
- nodes
- `__init__.py` Invoke-managed custom node loader
- cool_node
- `__init__.py` see example below
- cool_node.py
- my_node_pack
- `__init__.py` see example below
- tasty_node.py
- bodacious_node.py
- utils.py
- extra_nodes
- fancy_node.py
</FileTree>
Each node folder must have an `__init__.py` file that imports its nodes. Only
nodes imported in the `__init__.py` file are loaded. See the README in the nodes
folder for more examples:
```py title="__init__.py"
from .cool_node import ResizeInvocation
````
## Creating A New Invocation
In order to understand the process of creating a new Invocation, let us actually
create one.
In our example, let us create an Invocation that will take in an image, resize
it and output the resized image.
The first set of things we need to do when creating a new Invocation are -
<Steps>
1. Create a new class that derives from a predefined parent class called `BaseInvocation`.
2. Every Invocation must have a `docstring` that describes what this Invocation does.
3. While not strictly required, we suggest every invocation class name ends in "Invocation", eg "CropImageInvocation".
4. Every Invocation must use the `@invocation` decorator to provide its unique invocation type. You may provide its title, tags and category using the decorator.
5. Invocations are strictly typed. We make use of the native [typing](https://docs.python.org/3/library/typing.html) library and the installed [pydantic](https://pydantic-docs.helpmanual.io/) library for validation.
</Steps>
So let us do that.
```py title="resize.py"
from invokeai.invocation_api import (
BaseInvocation,
invocation,
)
@invocation('resize')
class ResizeInvocation(BaseInvocation):
'''Resizes an image'''
```
That's great.
Now we have setup the base of our new Invocation. Let us think about what inputs
our Invocation takes.
- We need an `image` that we are going to resize.
- We will need new `width` and `height` values to which we need to resize the
image to.
### Inputs
Every Invocation input must be defined using the `InputField` function. This is
a wrapper around the pydantic `Field` function, which handles a few extra things
and provides type hints. Like everything else, this should be strictly typed and
defined.
So let us create these inputs for our Invocation. First up, the `image` input we
need. Generally, we can use standard variable types in Python but InvokeAI
already has a custom `ImageField` type that handles all the stuff that is needed
for image inputs.
But what is this `ImageField` ..? It is a special class type specifically
written to handle how images are dealt with in InvokeAI. We will cover how to
create your own custom field types later in this guide. For now, let's go ahead
and use it.
```py title="resize.py"
from invokeai.invocation_api import (
BaseInvocation,
ImageField,
InputField,
invocation,
)
@invocation('resize')
class ResizeInvocation(BaseInvocation):
# Inputs
image: ImageField = InputField(description="The input image")
```
Let us break down our input code.
```python
image: ImageField = InputField(description="The input image")
```
| Part | Value | Description |
| ---- | ----- | ----------- |
| Name | `image` | The variable that will hold our image. |
| Type Hint | `ImageField` | The type for our field. Indicates that `image` must be an `ImageField`. |
| Field | `InputField(description="The input image")` | Declares `image` as an input field and provides its description. |
Great. Now let us create our other inputs for `width` and `height`
```py title="resize.py"
from invokeai.invocation_api import (
BaseInvocation,
ImageField,
InputField,
invocation,
)
@invocation('resize')
class ResizeInvocation(BaseInvocation):
# Inputs
image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
```
As you might have noticed, we added two new arguments to the `InputField`
definition for `width` and `height`, called `gt` and `le`. They stand for
_greater than or equal to_ and _less than or equal to_.
These impose constraints on those fields, and will raise an exception if the
values do not meet the constraints. Field constraints are provided by
**pydantic**, so anything you see in the **pydantic docs** will work.
**Note:** _Any time it is possible to define constraints for our field, we
should do it so the frontend has more information on how to parse this field._
Perfect. We now have our inputs. Let us do something with these.
### Invoke Function
The `invoke` function is where all the magic happens. This function provides you
the `context` parameter that is of the type `InvocationContext` which will give
you access to the current context of the generation and all the other services
that are provided by it by InvokeAI.
Let us create this function first.
```py title="resize.py"
from invokeai.invocation_api import (
BaseInvocation,
ImageField,
InputField,
InvocationContext,
invocation,
)
@invocation('resize')
class ResizeInvocation(BaseInvocation):
'''Resizes an image'''
image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
def invoke(self, context: InvocationContext):
pass
```
### Outputs
The output of our Invocation will be whatever is returned by this `invoke`
function. Like with our inputs, we need to strongly type and define our outputs
too.
What is our output going to be? Another image. Normally you'd have to create a
type for this but InvokeAI already offers you an `ImageOutput` type that handles
all the necessary info related to image outputs. So let us use that.
We will cover how to create your own output types later in this guide.
```py title="resize.py"
from invokeai.invocation_api import (
BaseInvocation,
ImageField,
InputField,
InvocationContext,
invocation,
)
from invokeai.app.invocations.image import ImageOutput
@invocation('resize')
class ResizeInvocation(BaseInvocation):
'''Resizes an image'''
image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
def invoke(self, context: InvocationContext) -> ImageOutput:
pass
```
Perfect. Now that we have our Invocation setup, let us do what we want to do.
- We will first load the image using one of the services provided by InvokeAI to
load the image.
- We will resize the image using `PIL` to our input data.
- We will output this image in the format we set above.
So let's do that.
```py title="resize.py"
from invokeai.invocation_api import (
BaseInvocation,
ImageField,
InputField,
InvocationContext,
invocation,
)
from invokeai.app.invocations.image import ImageOutput
@invocation("resize")
class ResizeInvocation(BaseInvocation):
"""Resizes an image"""
image: ImageField = InputField(description="The input image")
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
def invoke(self, context: InvocationContext) -> ImageOutput:
# Load the input image as a PIL image
image = context.images.get_pil(self.image.image_name)
# Resize the image
resized_image = image.resize((self.width, self.height))
# Save the image
image_dto = context.images.save(image=resized_image)
# Return an ImageOutput
return ImageOutput.build(image_dto)
```
**Note:** Do not be overwhelmed by the `ImageOutput` process. InvokeAI has a
certain way that the images need to be dispatched in order to be stored and read
correctly. In 99% of the cases when dealing with an image output, you can simply
copy-paste the template above.
### Customization
We can use the `@invocation` decorator to provide some additional info to the
UI, like a custom title, tags and category.
We also encourage providing a version. This must be a
[semver](https://semver.org/) version string ("`$MAJOR`.`$MINOR`.`$PATCH`"). The UI
will let users know if their workflow is using a mismatched version of the node.
```py title="resize.py"
@invocation("resize", title="My Resizer", tags=["resize", "image"], category="My Invocations", version="1.0.0")
class ResizeInvocation(BaseInvocation):
"""Resizes an image"""
image: ImageField = InputField(description="The input image")
# Rest of the code
```
That's it. You made your own **Resize Invocation**.
## Result
Once you make your Invocation correctly, the rest of the process is fully
automated for you.
When you launch InvokeAI, you can go to `http://localhost:9090/docs` and see
your new Invocation show up there with all the relevant info.
![resize invocation](./assets/resize_invocation.png)
When you launch the frontend UI, you can go to the Node Editor tab and find your
new Invocation ready to be used.
![resize node editor](./assets/resize_node_editor.png)
## Contributing Nodes
Once you've created a Node, the next step is to share it with the community! The
best way to do this is to submit a Pull Request to add the Node to the
[Community Nodes](/features/workflows/community-nodes) list. If you're not sure how to do that,
take a look a at our [contributing nodes overview](/development/guides/creating-nodes/).
## Advanced
### Custom Output Types
Like with custom inputs, sometimes you might find yourself needing custom
outputs that InvokeAI does not provide. We can easily set one up.
Now that you are familiar with Invocations and Inputs, let us use that knowledge
to create an output that has an `image` field, a `color` field and a `string`
field.
- An invocation output is a class that derives from the parent class of
`BaseInvocationOutput`.
- All invocation outputs must use the `@invocation_output` decorator to provide
their unique output type.
- Output fields must use the provided `OutputField` function. This is very
similar to the `InputField` function described earlier - it's a wrapper around
`pydantic`'s `Field()`.
- It is not mandatory but we recommend using names ending with `Output` for
output types.
- It is not mandatory but we highly recommend adding a `docstring` to describe
what your output type is for.
Now that we know the basic rules for creating a new output type, let us go ahead
and make it.
```py title="custom_output.py"
from .baseinvocation import BaseInvocationOutput, OutputField, invocation_output
from .primitives import ImageField, ColorField
@invocation_output('image_color_string_output')
class ImageColorStringOutput(BaseInvocationOutput):
'''Base class for nodes that output a single image'''
image: ImageField = OutputField(description="The image")
color: ColorField = OutputField(description="The color")
text: str = OutputField(description="The string")
```
That's all there is to it.
### Custom Input Fields
Now that you know how to create your own Invocations, let us dive into slightly
more advanced topics.
While creating your own Invocations, you might run into a scenario where the
existing fields in InvokeAI do not meet your requirements. In such cases, you
can create your own fields.
Let us create one as an example. Let us say we want to create a color input
field that represents a color code. But before we start on that here are some
general good practices to keep in mind.
### Best Practices
- There is no naming convention for input fields, but we highly recommend that
you name it something appropriate like `ColorField`.
- It is not mandatory but it is heavily recommended to add a relevant
`docstring` to describe your field.
- Keep your field in the same file as the Invocation that it is made for, or in
another file where it is relevant.
All input types are a class that derive from the `BaseModel` type from `pydantic`.
So let's create one.
```py title="color_field.py"
from pydantic import BaseModel
class ColorField(BaseModel):
'''A field that holds the rgba values of a color'''
pass
```
Perfect. Now let us create the properties for our field. This is similar to how
you created input fields for your Invocation. All the same rules apply. Let us
create four fields representing the _red(r)_, _blue(b)_, _green(g)_ and
_alpha(a)_ channel of the color.
:::note
Technically, the properties are _also_ called fields - but in this case, it refers to a `pydantic` field.
:::
```py title="color_field.py"
class ColorField(BaseModel):
'''A field that holds the rgba values of a color'''
r: int = Field(ge=0, le=255, description="The red channel")
g: int = Field(ge=0, le=255, description="The green channel")
b: int = Field(ge=0, le=255, description="The blue channel")
a: int = Field(ge=0, le=255, description="The alpha channel")
```
That's it. We now have a new input field type that we can use in our Invocations
like this.
```python
color: ColorField = InputField(default=ColorField(r=0, g=0, b=0, a=0), description='Background color of an image')
```
### Using the custom field
When you start the UI, your custom field will be automatically recognized.
Custom fields only support connection inputs in the Workflow Editor.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,104 @@
---
title: Architecture Overview
sidebar:
order: 1
label: Overview
lastUpdated: 2026-02-18
---
import Mermaid from '@components/Mermaid.astro'
<Mermaid>
```mermaid
flowchart TB
subgraph apps[Applications]
webui[WebUI]
cli[CLI]
subgraph webapi[Web API]
api[HTTP API]
sio[Socket.IO]
end
end
subgraph invoke[Invoke]
direction LR
invoker
services
sessions
invocations
end
subgraph core[AI Core]
Generate
end
webui --> webapi
webapi --> invoke
cli --> invoke
invoker --> services & sessions
invocations --> services
sessions --> invocations
services --> core
%% Styles
classDef sg fill:#5028C8,font-weight:bold,stroke-width:2,color:#fff,stroke:#14141A
classDef default stroke-width:2px,stroke:#F6B314,color:#fff,fill:#14141A
class apps,webapi,invoke,core sg
```
</Mermaid>
## Applications
Applications are built on top of the invoke framework. They should construct `invoker` and then interact through it. They should avoid interacting directly with core code in order to support a variety of configurations.
### Web UI
The Web UI is built on top of an HTTP API built with [FastAPI](https://fastapi.tiangolo.com/) and [Socket.IO](https://socket.io/). The frontend code is found in `/invokeai/frontend` and the backend code is found in `/invokeai/app/api_app.py` and `/invokeai/app/api/`. The code is further organized as such:
| Component | Description |
| --- | --- |
| api_app.py | Sets up the API app, annotates the OpenAPI spec with additional data, and runs the API |
| dependencies | Creates all invoker services and the invoker, and provides them to the API |
| events | An eventing system that could in the future be adapted to support horizontal scale-out |
| sockets | The Socket.IO interface - handles listening to and emitting session events (events are defined in the events service module) |
| routers | API definitions for different areas of API functionality |
### CLI
The CLI is built automatically from invocation metadata, and also supports invocation piping and auto-linking. Code is available in `/invokeai/frontend/cli`.
## Invoke
The Invoke framework provides the interface to the underlying AI systems and is built with flexibility and extensibility in mind. There are four major concepts: invoker, sessions, invocations, and services.
### Invoker
The invoker (`/invokeai/app/services/invoker.py`) is the primary interface through which applications interact with the framework. Its primary purpose is to create, manage, and invoke sessions. It also maintains two sets of services:
- **invocation services**, which are used by invocations to interact with core functionality.
- **invoker services**, which are used by the invoker to manage sessions and manage the invocation queue.
### Sessions
Invocations and links between them form a graph, which is maintained in a session. Sessions can be queued for invocation, which will execute their graph (either the next ready invocation, or all invocations). Sessions also maintain execution history for the graph (including storage of any outputs). An invocation may be added to a session at any time, and there is capability to add and entire graph at once, as well as to automatically link new invocations to previous invocations. Invocations can not be deleted or modified once added.
The session graph does not support looping. This is left as an application problem to prevent additional complexity in the graph.
### Invocations
Invocations represent individual units of execution, with inputs and outputs. All invocations are located in `/invokeai/app/invocations`, and are all automatically discovered and made available in the applications. These are the primary way to expose new functionality in Invoke.AI, and the [implementation guide](/development/architecture/invocations/) explains how to add new invocations.
### Services
Services provide invocations access AI Core functionality and other necessary functionality (e.g. image storage). These are available in `/invokeai/app/services`. As a general rule, new services should provide an interface as an abstract base class, and may provide a lightweight local implementation by default in their module. The goal for all services should be to enable the usage of different implementations (e.g. using cloud storage for image storage), but should not load any module dependencies unless that implementation has been used (i.e. don't import anything that won't be used, especially if it's expensive to import).
## AI Core
The AI Core is represented by the rest of the code base (i.e. the code outside of `/invokeai/app/`).
@@ -0,0 +1,314 @@
---
title: Documentation
lastUpdated: 2026-05-14
---
import { Steps, Tabs, TabItem, FileTree } from '@astrojs/starlight/components'
The Invoke AI website, including its documentation are all contained within the `docs` directory.
## Prerequisites
The documentation is built using [Astro Starlight](https://starlight.astro.build/). It's suggested you familiarize yourself with the following technologies before getting started:
<Steps>
1. [Markdown](https://www.markdownguide.org/) - a lightweight markup language for creating formatted text.
2. [MDX](https://mdxjs.com/) - a superset of Markdown that allows you to use React components in your content.
3. [Astro](https://astro.build/) - a modern static site builder that supports MDX and other front-end technologies.
4. [Starlight](https://starlight.astro.build/) - a theme for Astro that provides a clean and modern documentation experience.
5. [Vite](https://vitejs.dev/) - a fast development server and build tool for modern web projects.
</Steps>
Markdown powers the content of every page on the website (including the homepage), with additional help from [MDX](https://mdxjs.com/) to make the pages more interactive with imported React components.
## Navigating the Documentation
The documentation is organized into a file tree structure. It should be very familiar to anyone who has built modern web applications.
<FileTree>
- docs/
- dist/ production build output
- public/ non-optimized, public assets
- src/ main source code
- assets/ optimized assets
- config/ astro/starlight configs
- content/ markdown pages and content
- docs/ documentation content
- i18n/ internationalized content
- generated/ generated json files for dynamic content
- layouts/ components used to wrap pages
- lib/ utility functions and shared code
- components/ reusable, custom components
- pages/ non-documentation pages
- styles/ global styles and themes
</FileTree>
## Development
If you've ever worked within a react, astro or similar node-based library or framework, you should feel familiar with most of the setup here.
If you're adding a feature, new behavior or etc. that changes how users expect Invoke to work, we expect you to deliver your PR with associated docs to support it. To get started, follow the steps below.
### Dev Environment
There are 2 main ways to get your development environment set up for documentation:
<Tabs syncKey="docsDevMethod">
<TabItem label="Make">
Invoke's makefile makes it easy to set up your development environment for documentation in only a couple of commands. You can run these from the root of the repository.
<Steps>
1. First, install the required dependencies.
```sh
make docs-install
```
2. Next, run the development server.
```sh
make docs-dev
```
3. Open your browser and navigate to `http://localhost:4321` to view the documentation.
</Steps>
</TabItem>
<TabItem label="Manual">
If you prefer good ol' fashioned `cd` and `pnpm` commands, you can set up your development environment manually.
<Steps>
1. First, cd into the docs directory.
```sh
cd docs
```
2. Next, install the required dependencies.
```sh
pnpm install
```
3. Run the development server.
```sh
pnpm dev
```
4. Open your browser and navigate to `http://localhost:4321` to view the documentation.
</Steps>
</TabItem>
</Tabs>
If there's another local server running on port `4321` prior to running this, then use the port specified in the output.
### Adding Pages
Located within the `src/content/docs/` directory, this is where the documentation pages are stored and organized by category. These categories are file-based and are mirrored to the sidebar navigation.
:::caution
Do not place your docs content contributions outside of the `content` directory, it will not be seen.
:::
If you wish to add a new sub category to document a feature or a behavior, simply create a new directory within the relevant top-level category directory.
For example, if we wanted to document a new feature called "Instant Bananas", we would create a new directory within `src/content/docs/features/` like so:
`src/content/docs/`
<FileTree>
- concepts/
- configuration/
- contributing/
- development/
- features/
- **instant-bananas/**
- **index.md** Write your documentation here
- **requirements.mdx** You can add more pages in this directory
- start-here/
- troubleshooting/
- workflows/
</FileTree>
The way you organize your added pages dictates how the URL structure is generated for your documentation pages. In this example, the url for the `index.md` page would be `https://invoke.ai/features/instant-bananas/`, and the url for the `requirements.mdx` page would be `https://invoke.ai/features/instant-bananas/requirements/`.
If you wish to add a top-level category, then one additional step is required for the category to appear in the sidebar.
Within the `src/config/sidebar.ts` file, you'll need to add a new sidebar category object to the array, since the fine-grained control over top-level categories needs to be a bit more explicit.
```diff lang="js"
const sidebar = [
// ...
{
label: 'Concepts',
items: [
{
autogenerate: { directory: 'concepts' },
},
],
},
+ {
+ label: 'A New Category',
+ items: [
+ {
+ autogenerate: { directory: 'new-category' },
+ },
+ ],
+ },
{
label: 'Features',
items: [
{
autogenerate: { directory: 'features' },
},
],
},
// ...
]
```
### Page Metadata
Before your page becomes available, you will need to add frontmatter to define the page's metadata such as its title, description, last update date, sidebar position, and etc.
Learn more about what frontmatter is and how to use it in your pages in the [Starlight Documentation](https://starlight.astro.build/reference/frontmatter/).
Once you have some basic frontmatter defined, you should be able to see it reflected in the sidebar and the page title.
### Adding Images
We encourage adding imagery to your docs for creating a more engaging and visual experience for viewers. To add images, we prefer you to utilize an `assets` directory within the concerning category.
<FileTree>
- features/
- instant-bananas/
- **assets/**
- **demonstration.webp**
- **foobar.avif**
- index.mdx
</FileTree>
The Astro image optimizer/renderer is quite flexible with image formats and sizes, but we'd prefer stored images to be at reasonable sizes (not 4k), and using optimized formats (webp, avif, jpeg).
To render the image, you'd just use a relative path in your markdown.
```md title="index.mdx"
![Alt text here](./assets/demonstration.webp)
```
### Adding Translations
Currently, the documentation is only available in English. If you wish to add translations for other languages, we've already laid the ground work for you to do so.
Firstly, add a new folder within the `src/content/i18n` directory, and create your translated version of the markdown file into the same path as the original.
For example:
<FileTree>
- src/
- content/
- docs/
- start-here/
- installation.mdx
- i18n/
- zh-CN Country code here
- start-here/
- installation.mdx
</FileTree>
We recommend simply copy/pasting the file and rewriting the text from there.
Learn more about the intricacies of translating Astro Starlight docs [here](https://starlight.astro.build/guides/i18n).
## Running a Build
Modifications to the docs may run fine on your machine, but as we've learned the hard way, GitHub pages flips that expectation completely. So, we've added some ways to ensure things work as expected before deploying.
Just like with the dev environment, you can build the docs one of two ways:
<Tabs syncKey="docsDevMethod">
<TabItem label="Make">
Invoke's makefile makes it easy to build the documentation in only a single command. You can run it from the root of the repository.
<Steps>
1. First, run the build command.
```sh
make docs-build
```
2. Finally, preview the output.
```sh
make docs-preview
```
</Steps>
And that's it.
:::tip[Deploy Target]
The make command here sets the `DEPLOY_TARGET` environment variable to `custom`, so that the final output matches what you'd expect from the final deployment to https://invoke.ai.
If you'd rather set a different deploy target, use the manual method.
:::
</TabItem>
<TabItem label="Manual">
If you prefer good ol' fashioned `cd` and `pnpm` commands, or to have granular control over environment variables, you can run the following:
<Steps>
1. First, cd into the `docs` directory.
```sh
cd docs
```
2. Next run the build command.
```sh
pnpm run build
```
3. Finally, preview the build.
```sh
pnpm run preview
```
The preview url will be available on the same port as the dev server.
</Steps>
</TabItem>
</Tabs>
## Generated Files
The Invoke API is always evolving, and quite large. Documenting all this by hand would be wildly impractical, so there's a script we've set up to pull all that data and generate relevant json files into `generated` directory.
These files are used for the [YAML Config](/configuration/invokeai-yaml) and [API Development](/development/guides/api-development) pages. If you're adding a feature that changes the yaml config, or the api then make sure to run `pnpm run generate-docs-data` to ensure tests pass, and that the docs are accurate in accordance to your updates.
## Testing
The docs contain tests for the following:
| Test | Description | Runs on... |
| -- | -- | -- |
| Link Checker | Checks for invalid, malformed or misdirected internal link URLs | Dev Server, Build, Deploy |
| Verify Deployment Output | Check to ensure the asset and page paths have the expected base paths dependent on deploy targets | Build, Deploy |
| Check Docs Data | Checks to ensure the generated files are accurate | Deploy |
## GitHub Actions
Once you've submitted your updated docs, either via pull request or a main push to your own fork, the `deploy-docs` action will run.
The `deploy-docs` action will install the necessary dependencies, run a build, test and serve the docs on github pages. Any failing deployments will require fixing before deploying.
## Troubleshooting
#### All the styles are missing and the links are wrong, what happened?
This commonly happens when the base path and the deploy target are mismatched, check those first and then run your build again.
#### Redirects aren't working on the production deployment, but they work locally, why?
Because GitHub Pages' SSR environment is lackluster, and thus doesn't handle backend redirects. We included a redirects configuration just in case GitHub ever grows a conscience, or if the docs ever get deployed someplace else.
@@ -0,0 +1,54 @@
---
title: Canvas Projects
---
Canvas projects serialize the current canvas into a portable `.invk` archive. The feature lives in `invokeai/frontend/web/src/features/controlLayers/` and is exposed in the canvas toolbar archive menu and the canvas context menu under **Project**.
## File format
`.invk` files are ZIP archives. The current manifest version is `1`.
Each archive contains:
| Target | Description |
| - | - |
| `manifest.json` | project metadata, including the archive version, app version, creation timestamp, and project name. |
| `canvas_state.json` | raster layers, control layers, inpaint masks, regional guidance, bounding box state, and selected/bookmarked entity identifiers. |
| `params.json` | generation parameter state. |
| `ref_images.json` | global reference image state. |
| `loras.json` | active LoRA state. |
| `images/` | image blobs referenced by the canvas or reference image state. |
The save path builds this archive in `useCanvasProjectSave.ts`. It collects all referenced `image_name` values, fetches each image from the server, writes successfully fetched files under `images/`, and downloads the ZIP with the `.invk` extension. Failed image fetches are logged and skipped rather than aborting the save.
## Image collection
Image references are collected by `collectImageNames()` in `canvasProjectFile.ts`.
The collector checks:
- Image objects in raster layers.
- Image objects in control layers.
- Image objects in inpaint masks.
- Image objects and IP Adapter / Flux Redux reference images in regional guidance.
- Global reference images, including cropped source images.
Image fetches are concurrency-limited with `processWithConcurrencyLimit()` so large projects do not flood the browser or backend with simultaneous requests.
## Loading and remapping
The load path is implemented in `useCanvasProjectLoad.ts`.
Loading validates `manifest.json`, requires `canvas_state.json`, and reads optional `params.json`, `ref_images.json`, and `loras.json` files. Before restoring state, it checks whether each referenced image already exists on the server with `checkExistingImages()`.
Only missing images are uploaded from the archive. If a referenced missing image is not present in `images/`, the loader logs a warning and leaves that reference unchanged. If an upload returns a different `image_name`, the loader records an old-to-new mapping and remaps image references before dispatching restored canvas and reference image state.
LoRAs are cleared before project LoRAs are recalled. This prevents LoRAs from the previous canvas session from leaking into the loaded project.
Image existence checks and uploads are also concurrency-limited.
## Compatibility notes
The archive stores references to models, LoRAs, and other generation resources, not the model files themselves. Loading a project on another install can restore the canvas images and state, but missing model resources still need to be installed or replaced by the user.
Future format changes should increment `CANVAS_PROJECT_VERSION` and keep validation in `parseManifest()` explicit so unsupported project files fail early.
@@ -0,0 +1,131 @@
---
title: Frontend Development
lastUpdated: 2026-02-18
---
Invoke's UI is made possible by many contributors and open-source libraries. Thank you!
## Dev environment
Follow the [dev environment](/development/setup/dev-environment/) guide to get set up. Run the UI using `pnpm dev`.
## Package scripts
- `dev`: run the frontend in dev mode, enabling hot reloading
- `build`: run all checks (dpdm, eslint, prettier, tsc, knip) and then build the frontend
- `lint:dpdm`: check circular dependencies
- `lint:eslint`: check code quality
- `lint:prettier`: check code formatting
- `lint:tsc`: check type issues
- `lint:knip`: check for unused exports or objects
- `lint`: run all checks concurrently
- `fix`: run `eslint` and `prettier`, fixing fixable issues
- `test:ui`: run `vitest` with the fancy web UI
## Type generation
We use [openapi-typescript] to generate types from the app's OpenAPI schema. The generated types are committed to the repo in [schema.ts].
If you make backend changes, it's important to regenerate the frontend types:
```sh
cd invokeai/frontend/web && python ../../../scripts/generate_openapi_schema.py | pnpm typegen
```
On macOS and Linux, you can run `make frontend-typegen` as a shortcut for the above snippet.
## Localization
We use [i18next] for localization, but translation to languages other than English happens on our [Weblate] project.
Only the English source strings (i.e. `en.json`) should be changed on this repo.
## VSCode
### Example debugger config
```jsonc
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Invoke UI",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/invokeai/frontend/web"
}
]
}
```
### Remote dev
We've noticed an intermittent timeout issue with the VSCode remote dev port forwarding.
We suggest disabling the editor's port forwarding feature and doing it manually via SSH:
```sh
ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host
```
## Contributing Guidelines
Thanks for your interest in contributing to the Invoke Web UI!
Please follow these guidelines when contributing.
## Check in before investing your time
Please check in before you invest your time on anything besides a trivial fix, in case it conflicts with ongoing work or isn't aligned with the vision for the app.
If a feature request or issue doesn't already exist for the thing you want to work on, please create one.
Ping `@psychedelicious` on [discord] in the `#frontend-dev` channel or in the feature request / issue you want to work on - we're happy to chat.
## Code conventions
- This is a fairly complex app with a deep component tree. Please use memoization (`useCallback`, `useMemo`, `memo`) with enthusiasm.
- If you need to add some global, ephemeral state, please use [nanostores] if possible.
- Be careful with your redux selectors. If they need to be parameterized, consider creating them inside a `useMemo`.
- Feel free to use `lodash` (via `lodash-es`) to make the intent of your code clear.
- Please add comments describing the "why", not the "how" (unless it is really arcane).
## Commit format
Please use the [conventional commits] spec for the web UI, with a scope of "ui":
- `chore(ui): bump deps`
- `chore(ui): lint`
- `feat(ui): add some cool new feature`
- `fix(ui): fix some bug`
## Tests
We don't do any UI testing at this time, but consider adding tests for sensitive logic.
We use `vitest`, and tests should be next to the file they are testing. If the logic is in `something.ts`, the tests should be in `something.test.ts`.
In some situations, we may want to test types. For example, if you use `zod` to create a schema that should match a generated type, it's best to add a test to confirm that the types match. Use `tsafe`'s assert for this.
## Submitting a PR
- Ensure your branch is tidy. Use an interactive rebase to clean up the commit history and reword the commit messages if they are not descriptive.
- Run `pnpm lint`. Some issues are auto-fixable with `pnpm fix`.
- Fill out the PR form when creating the PR.
- It doesn't need to be super detailed, but a screenshot or video is nice if you changed something visually.
- If a section isn't relevant, delete it.
## Other docs
- [Workflows - Design and Implementation]
- [State Management]
[discord]: https://discord.gg/ZmtBAhwWhy
[i18next]: https://github.com/i18next/react-i18next
[Weblate]: https://hosted.weblate.org/engage/invokeai/
[openapi-typescript]: https://github.com/openapi-ts/openapi-typescript
[schema.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/services/api/schema.ts
[conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/
[Workflows - Design and Implementation]: ./workflows/
[State Management]: ./state-management/
@@ -0,0 +1,41 @@
---
title: State Management
lastUpdated: 2026-02-18
---
The app makes heavy use of Redux Toolkit, its Query library, and `nanostores`.
## Redux
We use RTK extensively - slices, entity adapters, queries, reselect, the whole 9 yards. Their [docs](https://redux-toolkit.js.org/) are excellent.
## `nanostores`
[nanostores] is a tiny state management library. It provides both imperative and declarative APIs.
### Example
```ts
export const $myStringOption = atom<string | null>(null);
// Outside a component, or within a callback for performance-critical logic
$myStringOption.get();
$myStringOption.set('new value');
// Inside a component
const myStringOption = useStore($myStringOption);
```
### Where to put nanostores
- For global application state, export your stores from `invokeai/frontend/web/src/app/store/nanostores/`.
- For feature state, create a file for the stores next to the redux slice definition (e.g. `invokeai/frontend/web/src/features/myFeature/myFeatureNanostores.ts`).
- For hooks with global state, export the store from the same file the hook is in, or put it next to the hook.
### When to use nanostores
- For non-serializable data that needs to be available throughout the app, use `nanostores` instead of a global.
- For ephemeral global state (i.e. state that does not need to be persisted), use `nanostores` instead of redux.
- For performance-critical code and in callbacks, redux selectors can be problematic due to the declarative reactivity system. Consider refactoring to use `nanostores` if there's a **measurable** performance issue.
[nanostores]: https://github.com/nanostores/nanostores/
@@ -0,0 +1,37 @@
---
title: "Canvas Text Tool"
---
## Overview
The canvas text workflow is split between a Konva module that owns tool state and a React overlay that handles text entry.
- `invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasTextToolModule.ts`
- Owns the tool, cursor preview, and text session state (including the cursor "T" marker).
- Manages dynamic cursor contrast, starts sessions on pointer down, and commits sessions by rasterizing the active text block into a new raster layer.
- `invokeai/frontend/web/src/features/controlLayers/components/Text/CanvasTextOverlay.tsx`
- Renders the on-canvas editor as a `contentEditable` overlay positioned in canvas space.
- Syncs keyboard input, suppresses app hotkeys, and forwards commits/cancels to the Konva module.
- `invokeai/frontend/web/src/features/controlLayers/components/Text/TextToolOptions.tsx`
- Provides the font dropdown, size slider/input, formatting toggles, and alignment buttons that appear when the Text tool is active.
## Rasterization pipeline
`renderTextToCanvas()` (`invokeai/frontend/web/src/features/controlLayers/text/textRenderer.ts`) converts the editor contents into a transparent canvas. The Text tool module configures the renderer with the active font stack, weight, styling flags, alignment, and the active canvas color. The resulting canvas is encoded to a PNG data URL and stored in a new raster layer (`image` object) with a transparent background.
Layer placement preserves the original click location:
- The session stores the anchor coordinate (where the user clicked) and current alignment.
- `calculateLayerPosition()` calculates the top-left position for the raster layer after applying the configured padding and alignment offsets.
- New layers are inserted directly above the currently-selected raster layer (when present) and selected automatically.
## Font stacks
Font definitions live in `invokeai/frontend/web/src/features/controlLayers/text/textConstants.ts` as ten deterministic stacks (sans, serif, mono, rounded, script, humanist, slab serif, display, narrow, UI serif). Each stack lists system-safe fallbacks so the editor can choose the first available font per platform.
To add or adjust fonts:
1. Update `TEXT_FONT_STACKS` with the new `id`, `label`, and CSS `font-family` stack.
2. If you add a new stack, extend the `TEXT_FONT_IDS` tuple and update the `canvasTextSlice` schema default (`TEXT_DEFAULT_FONT_ID`).
3. Provide translation strings for any new labels in `public/locales/*`.
4. The editor and renderer will automatically pick up the new stack via `getFontStackById()`.
@@ -0,0 +1,315 @@
---
title: Workflows
lastUpdated: 2026-02-18
---
This document describes, at a high level, the design and implementation of workflows in the InvokeAI frontend. There are a substantial number of implementation details not included, but which are hopefully clear from the code.
InvokeAI's backend uses graphs, composed of **nodes** and **edges**, to process data and generate images.
Nodes have any number of **input fields** and **output fields**. Edges connect nodes together via their inputs and outputs. Fields have data types which dictate how they may be connected.
During execution, a nodes' outputs may be passed along to any number of other nodes' inputs.
Workflows are an enriched abstraction over a graph.
## Design
InvokeAI provide two ways to build graphs in the frontend: the [Linear UI](#linear-ui) and [Workflow Editor](#workflow-editor).
To better understand the use case and challenges related to workflows, we will review both of these modes.
### Linear UI
This includes the **Text to Image**, **Image to Image** and **Unified Canvas** tabs.
The user-managed parameters on these tabs are stored as simple objects in the application state. When the user invokes, adding a generation to the queue, we internally build a graph from these parameters.
This logic can be fairly complex due to the range of features available and their interactions. Depending on the parameters selected, the graph may be very different. Building graphs in code can be challenging - you are trying to construct a non-linear structure in a linear context.
The simplest graph building logic is for **Text to Image** with a SD1.5 model: [buildLinearTextToImageGraph.ts]
There are many other graph builders in the same directory for different tabs or base models (e.g. SDXL). Some are pretty hairy.
In the Linear UI, we go straight from **simple application state** to **graph** via these builders.
### Workflow Editor
The Workflow Editor is a visual graph editor, allowing users to draw edges from node to node to construct a graph. This _far_ more approachable way to create complex graphs.
InvokeAI uses the [reactflow] library to power the Workflow Editor. It provides both a graph editor UI and manages its own internal graph state.
#### Workflows
A workflow is a representation of a graph plus additional metadata:
- Name
- Description
- Version
- Notes
- [Exposed fields](#workflow-linear-view)
- Author, tags, category, etc.
Workflows should have other qualities:
- Portable: you should be able to load a workflow created by another person.
- Resilient: you should be able to "upgrade" a workflow as the application changes.
- Abstract: as much as is possible, workflows should not be married to the specific implementation details of the application.
To support these qualities, workflows are serializable, have a versioned schemas, and represent graphs as minimally as possible. Fortunately, the reactflow state for nodes and edges works perfectly for this.
##### Workflow -> reactflow state -> InvokeAI graph
Given a workflow, we need to be able to derive reactflow state and/or an InvokeAI graph from it.
The first step - workflow to reactflow state - is very simple. The logic is in [nodesSlice.ts], in the `workflowLoaded` reducer.
The reactflow state is, however, structurally incompatible with our backend's graph structure. When a user invokes on a Workflow, we need to convert the reactflow state into an InvokeAI graph. This is far simpler than the graph building logic from the Linear UI:
[buildNodesGraph.ts]
##### Nodes vs Invocations
We often use the terms "node" and "invocation" interchangeably, but they may refer to different things in the frontend.
reactflow [has its own definitions][reactflow-concepts] of "node", "edge" and "handle" which are closely related to InvokeAI graph concepts.
- A reactflow node is related to an InvokeAI invocation. It has a "data" property, which holds the InvokeAI-specific invocation data.
- A reactflow edge is roughly equivalent to an InvokeAI edge.
- A reactflow handle is roughly equivalent to an InvokeAI input or output field.
##### Workflow Linear View
Graphs are very capable data structures, but not everyone wants to work with them all the time.
To allow less technical users - or anyone who wants a less visually noisy workspace - to benefit from the power of nodes, InvokeAI has a workflow feature called the Linear View.
A workflow input field can be added to this Linear View, and its input component can be presented similarly to the Linear UI tabs. Internally, we add the field to the workflow's list of exposed fields.
#### OpenAPI Schema
OpenAPI is a schema specification that can represent complex data structures and relationships. The backend is capable of generating an OpenAPI schema for all invocations.
When the UI connects, it requests this schema and parses each invocation into an **invocation template**. Invocation templates have a number of properties, like title, description and type, but the most important ones are their input and output **field templates**.
Invocation and field templates are the "source of truth" for graphs, because they indicate what the backend is able to process.
When a user adds a new node to their workflow, these templates are used to instantiate a node with fields instantiated from the input and output field templates.
##### Field Instances and Templates
Field templates consist of:
- Name: the identifier of the field, its variable name in python
- Type: derived from the field's type annotation in python (e.g. IntegerField, ImageField, MainModelField)
- Constraints: derived from the field's creation args in python (e.g. minimum value for an integer)
- Default value: optionally provided in the field's creation args (e.g. 42 for an integer)
Field instances are created from the templates and have name, type and optionally a value.
The type of the field determines the UI components that are rendered for it.
A field instance's name associates it with its template.
##### Stateful vs Stateless Fields
**Stateful** fields store their value in the frontend graph. Think primitives, model identifiers, images, etc. Fields are only stateful if the frontend allows the user to directly input a value for them.
Many field types, however, are **stateless**. An example is a `UNetField`, which contains some data describing a UNet. Users cannot directly provide this data - it is created and consumed in the backend.
Stateless fields do not store their value in the node, so their field instances do not have values.
"Custom" fields will always be treated as stateless fields.
##### Single and Collection Fields
Field types have a name and cardinality property which may identify it as a **SINGLE**, **COLLECTION** or **SINGLE_OR_COLLECTION** field.
- If a field is annotated in python as a singular value or class, its field type is parsed as a **SINGLE** type (e.g. `int`, `ImageField`, `str`).
- If a field is annotated in python as a list, its field type is parsed as a **COLLECTION** type (e.g. `list[int]`).
- If it is annotated as a union of a type and list, the type will be parsed as a **SINGLE_OR_COLLECTION** type (e.g. `Union[int, list[int]]`). Fields may not be unions of different types (e.g. `Union[int, list[str]]` and `Union[int, str]` are not allowed).
## Implementation
The majority of data structures in the backend are [pydantic] models. Pydantic provides OpenAPI schemas for all models and we then generate TypeScript types from those.
The OpenAPI schema is parsed at runtime into our invocation templates.
Workflows and all related data are modeled in the frontend using [zod]. Related types are inferred from the zod schemas.
> In python, invocations are pydantic models with fields. These fields become node inputs. The invocation's `invoke()` function returns a pydantic model - its output. Like the invocation itself, the output model has any number of fields, which become node outputs.
### zod Schemas and Types
The zod schemas, inferred types, and type guards are in [types/].
Roughly order from lowest-level to highest:
- `common.ts`: stateful field data, and couple other misc types
- `field.ts`: fields - types, values, instances, templates
- `invocation.ts`: invocations and other node types
- `workflow.ts`: workflows and constituents
We customize the OpenAPI schema to include additional properties on invocation and field schemas. To facilitate parsing this schema into templates, we modify/wrap the types from [openapi-types] in `openapi.ts`.
### OpenAPI Schema Parsing
The entrypoint for OpenAPI schema parsing is [parseSchema.ts].
General logic flow:
- Iterate over all invocation schema objects
- Extract relevant invocation-level attributes (e.g. title, type, version, etc)
- Iterate over the invocation's input fields
- [Parse each field's type](#parsing-field-types)
- [Build a field input template](#building-field-input-templates) from the type - either a stateful template or "generic" stateless template
- Iterate over the invocation's output fields
- Parse the field's type (same as inputs)
- [Build a field output template](#building-field-output-templates)
- Assemble the attributes and fields into an invocation template
Most of these involve very straightforward `reduce`s, but the less intuitive steps are detailed below.
#### Parsing Field Types
Field types are represented as structured objects:
```ts
type FieldType = {
name: string;
cardinality: 'SINGLE' | 'COLLECTION' | 'SINGLE_OR_COLLECTION';
};
```
The parsing logic is in `parseFieldType.ts`.
There are 4 general cases for field type parsing.
##### Primitive Types
When a field is annotated as a primitive values (e.g. `int`, `str`, `float`), the field type parsing is fairly straightforward. The field is represented by a simple OpenAPI **schema object**, which has a `type` property.
We create a field type name from this `type` string (e.g. `string` -> `StringField`). The cardinality is `"SINGLE"`.
##### Complex Types
When a field is annotated as a pydantic model (e.g. `ImageField`, `MainModelField`, `ControlField`), it is represented as a **reference object**. Reference objects are pointers to another schema or reference object within the schema.
We need to **dereference** the schema to pull these out. Dereferencing may require recursion. We use the reference object's name directly for the field type name.
> Unfortunately, at this time, we've had limited success using external libraries to deference at runtime, so we do this ourselves.
##### Collection Types
When a field is annotated as a list of a single type, the schema object has an `items` property. They may be a schema object or reference object and must be parsed to determine the item type.
We use the item type for field type name. The cardinality is `"COLLECTION"`.
##### Single or Collection Types
When a field is annotated as a union of a type and list of that type, the schema object has an `anyOf` property, which holds a list of valid types for the union.
After verifying that the union has two members (a type and list of the same type), we use the type for field type name, with cardinality `"SINGLE_OR_COLLECTION"`.
##### Optional Fields
In OpenAPI v3.1, when an object is optional, it is put into an `anyOf` along with a primitive schema object with `type: 'null'`.
Handling this adds a fair bit of complexity, as we now must filter out the `'null'` types and work with the remaining types as described above.
If there is a single remaining schema object, we must recursively call to `parseFieldType()` to get parse it.
#### Building Field Input Templates
Now that we have a field type, we can build an input template for the field.
Stateful fields all get a function to build their template, while stateless fields are constructed directly. This is possible because stateless fields have no default value or constraints.
See [buildFieldInputTemplate.ts].
#### Building Field Output Templates
Field outputs are similar to stateless fields - they do not have any value in the frontend. When building their templates, we don't need a special function for each field type.
See [buildFieldOutputTemplate.ts].
### Managing reactflow State
As described above, the workflow editor state is the essentially the reactflow state, plus some extra metadata.
We provide reactflow with an array of nodes and edges via redux, and a number of [event handlers][reactflow-events]. These handlers dispatch redux actions, managing nodes and edges.
The pieces of redux state relevant to workflows are:
- `state.nodes.nodes`: the reactflow nodes state
- `state.nodes.edges`: the reactflow edges state
- `state.nodes.workflow`: the workflow metadata
#### Building Nodes and Edges
A reactflow node has a few important top-level properties:
- `id`: unique identifier
- `type`: a string that maps to a react component to render the node
- `position`: XY coordinates
- `data`: arbitrary data
When the user adds a node, we build **invocation node data**, storing it in `data`. Invocation properties (e.g. type, version, label, etc.) are copied from the invocation template. Inputs and outputs are built from the invocation template's field templates.
See [buildInvocationNode.ts].
Edges are managed by reactflow, but briefly, they consist of:
- `source`: id of the source node
- `sourceHandle`: id of the source node handle (output field)
- `target`: id of the target node
- `targetHandle`: id of the target node handle (input field)
> Edge creation is gated behind validation logic. This validation compares the input and output field types and overall graph state.
#### Building a Workflow
Building a workflow entity is as simple as dropping the nodes, edges and metadata into an object.
Each node and edge is parsed with a zod schema, which serves to strip out any unneeded data.
See [buildWorkflow.ts].
#### Loading a Workflow
Workflows may be loaded from external sources or the user's local instance. In all cases, the workflow needs to be handled with care, as an untrusted object.
Loading has a few stages which may throw or warn if there are problems:
- Parsing the workflow data structure itself, [migrating](#workflow-migrations) it if necessary (throws)
- Check for a template for each node (warns)
- Check each node's version against its template (warns)
- Validate the source and target of each edge (warns)
This validation occurs in [validateWorkflow.ts].
If there are no fatal errors, the workflow is then stored in redux state.
### Workflow Migrations
When the workflow schema changes, we may need to perform some data migrations. This occurs as workflows are loaded. zod schemas for each workflow schema version is retained to facilitate migrations.
Previous schemas are in folders in `invokeai/frontend/web/src/features/nodes/types/`, eg `v1/`.
Migration logic is in [migrations.ts].
[pydantic]: https://github.com/pydantic/pydantic 'pydantic'
[zod]: https://github.com/colinhacks/zod 'zod'
[openapi-types]: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-types 'openapi-types'
[reactflow]: https://github.com/xyflow/xyflow 'reactflow'
[reactflow-concepts]: https://reactflow.dev/learn/concepts/terms-and-definitions
[reactflow-events]: https://reactflow.dev/api-reference/react-flow#event-handlers
[buildWorkflow.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts
[nodesSlice.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts
[buildLinearTextToImageGraph.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearTextToImageGraph.ts
[buildNodesGraph.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/graph/buildNodesGraph.ts
[buildInvocationNode.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/node/buildInvocationNode.ts
[validateWorkflow.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/workflow/validateWorkflow.ts
[migrations.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/workflow/migrations.ts
[parseSchema.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/schema/parseSchema.ts
[buildFieldInputTemplate.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/schema/buildFieldInputTemplate.ts
[buildFieldOutputTemplate.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/schema/buildFieldOutputTemplate.ts
@@ -0,0 +1,50 @@
---
title: API Development
---
import InvocationContextDocs from '@lib/components/InvocationContextDocs.astro'
Each invocation's `invoke` method is provided a single arg - the Invocation Context.
This object provides an API the invocation can use to interact with application services, for example:
- Saving images
- Logging messages
- Loading models
```py
class MyInvocation(BaseInvocation):
...
def invoke(self, context: InvocationContext) -> ImageOutput:
# Load an image
image_pil = context.images.get_pil(self.image.image_name)
# Do something to the image
output_image = do_something_cool(image_pil)
# Save the image
image_dto = context.images.save(output_image)
# Log a message
context.logger.info(f"Did something cool, image saved!")
# Return the output
return ImageOutput.build(image_dto)
...
```
The full generated API reference is documented below.
## Mixins
Two important mixins are provided to facilitate working with metadata and gallery boards.
### `WithMetadata`
Inherit from this class (in addition to `BaseInvocation`) to add a `metadata` input to your node. When you do this, you can access the metadata dict from `self.metadata` in the `invoke()` function.
The dict will be populated via the node's input, and you can add any metadata you'd like to it. When you call `context.images.save()`, if the metadata dict has any data, it be automatically embedded in the image.
### `WithBoard`
Inherit from this class (in addition to `BaseInvocation`) to add a `board` input to your node. This renders as a drop-down to select a board. The user's selection will be accessible from `self.board` in the `invoke()` function.
When you call `context.images.save()`, if a board was selected, the image will added to that board as it is saved.
<InvocationContextDocs />
Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

@@ -0,0 +1,157 @@
---
title: Creating Node Packs
lastUpdated: 2026-05-23
---
import { FileTree } from '@astrojs/starlight/components'
This guide explains how to structure your Git repository so it can be installed via InvokeAI's Custom Node Manager.
## Repository Structure
Your repository **is** the node pack. When a user installs it, the entire repo is cloned into the `nodes` directory.
### Minimum Required Structure
<FileTree>
- my-node-pack/
- `__init__.py` Required: Imports all node classes
- my_node.py Your node implementation(s)
- README.md Recommended: Describe how your nodes work
</FileTree>
The `__init__.py` at the root is **mandatory**. Without it, the pack will not be loaded.
### Recommended Structure
<FileTree>
- my-node-pack/
- `__init__.py` Required: Imports all node classes
- requirements.txt Python dependencies (user-installed)
- README.md Description, usage & examples
- node_one.py Node implementation
- node_two.py Node implementation
- utils.py Shared utilities
- workflows/ Optional: Included workflow files
- example_workflow.json
- advanced_workflow.json
</FileTree>
## The `__init__.py` File
This file must import all invocation classes you want to register. Only classes imported here will be available in InvokeAI.
```python title="__init__.py"
from .node_one import MyFirstInvocation
from .node_two import MySecondInvocation
```
If you have nodes in subdirectories:
```python
from .nodes.image_tools import CropInvocation, ResizeInvocation
from .nodes.text_tools import ConcatInvocation
```
## Dependencies (`requirements.txt` or `pyproject.toml`)
If your nodes require additional Python packages, list them in a `requirements.txt` (or `pyproject.toml`) at the repository root:
```txt title="requirements.txt"
numpy>=1.24
opencv-python>=4.8
```
The Custom Node Manager **does not** install these dependencies automatically — auto-installing into the running InvokeAI environment risks pulling in incompatible versions and breaking the application. After install, the UI shows the user a toast telling them that manual installation is required, and your README should document the exact install command (e.g. `pip install -r requirements.txt` from inside an activated InvokeAI environment).
**Important:** Avoid pinning versions too tightly. InvokeAI has its own dependencies, and version conflicts can cause issues. Use minimum version constraints (`>=`) where possible.
## Including Workflows
If your repository contains workflow `.json` files, they will be **automatically imported** into the user's workflow library during installation.
### Workflow Detection
The installer recursively scans your repository for `.json` files. A file is recognized as a workflow if it contains both `nodes` and `edges` keys at the top level.
### Tagging
Imported workflows are automatically tagged with `node-pack:<your-repo-name>` so users can filter for them in the workflow library. When the node pack is uninstalled, these workflows are also removed.
### Workflow Format
Workflows should follow the standard InvokeAI workflow format:
```json title="example_workflow.json"
{
"name": "My Example Workflow",
"author": "Your Name",
"description": "Demonstrates how to use MyFirstInvocation",
"version": "1.0.0",
"contact": "",
"tags": "example, my-node-pack",
"notes": "",
"meta": {
"version": "3.0.0",
"category": "user"
},
"exposedFields": [],
"nodes": [...],
"edges": [...]
}
```
**Tip:** The easiest way to create a workflow file is to build the workflow in InvokeAI's workflow editor, then export it via **Save As** and copy the `.json` file into your repository.
## Node Implementation
Each node is a Python class decorated with `@invocation()`. Here's a minimal example:
```python title="example_node.py"
from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation
from invokeai.app.invocations.fields import InputField, OutputField
from invokeai.invocation_api import BaseInvocationOutput, invocation_output
@invocation_output("my_output")
class MyOutput(BaseInvocationOutput):
result: str = OutputField(description="The result")
@invocation(
"my_node",
title="My Node",
tags=["example", "custom"],
category="custom",
version="1.0.0",
)
class MyInvocation(BaseInvocation):
"""Does something useful."""
input_text: str = InputField(default="", description="Input text")
def invoke(self, context) -> MyOutput:
return MyOutput(result=f"Processed: {self.input_text}")
```
For full details on the invocation API, see the [Invocation API documentation](invocation-api.md).
## Best Practices
- **Use a descriptive repository name** — it becomes the pack name shown in the UI
- **Include a README.md** with description, screenshots, and usage instructions
- **Version your nodes** using semver in the `@invocation()` decorator
- **Don't include large binary files** in your repository (models, weights, etc.)
- **Test your nodes** by placing the repo in the `nodes` directory before publishing
- **Include example workflows** so users can get started quickly
- **Tag your GitHub repository** with `invokeai-node` for discoverability
- **Avoid name collisions** — choose unique invocation type strings (e.g. `my_pack_resize` instead of just `resize`)
## Testing Your Pack
Before publishing, verify your pack works with the Custom Node Manager:
1. Create a Git repository with your node pack
2. Push it to GitHub (or any Git host)
3. In InvokeAI, go to the Nodes tab and install it via the Git URL
4. Verify your nodes appear in the workflow editor
5. Verify any included workflows are imported
6. Test uninstalling — nodes and workflows should be removed
@@ -0,0 +1,42 @@
---
title: Creating Nodes
---
import { Steps, LinkCard } from '@astrojs/starlight/components';
<Steps>
1. Learn about the specifics of creating a new node in our Node Creation Documentation.
<LinkCard
title="Invocations"
description="Learn about the invocation system, which is the foundation for creating nodes in InvokeAI."
href="../../architecture/invocations" />
2. Make sure the node is contained in a new Python (.py) file. Preferably, the node is in a repo with a README detailing the nodes usage & examples to help others more easily use your node. Including the tag "invokeai-node" in your repository's README can also help other users find it more easily.
3. Submit a pull request with a link to your node(s) repo in GitHub against the `main` branch to add the node to the [Community Nodes](../../../workflows/community-nodes) list
Make sure you are following the template below and have provided all relevant details about the node and what it does. Example output images and workflows are very helpful for other users looking to use your node.
4. A maintainer will review the pull request and node. If the node is aligned with the direction of the project, you may be asked for permission to include it in the core project.
</Steps>
### Community Node Template
Append the following template to your pull request and the [Community Nodes](../../../workflows/community-nodes) page when submitting a node to be added to the community nodes list:
```md
---
### Super Cool Node Template
**Description:** This node allows you to do super cool things with InvokeAI.
**Node Link:** https://github.com/invoke-ai/InvokeAI/fake_node.py
**Example Node Graph:** https://github.com/invoke-ai/InvokeAI/fake_node_graph.json
**Output Examples**
![InvokeAI](https://invoke-ai.github.io/InvokeAI/assets/invoke_ai_banner.png)
```
@@ -0,0 +1,556 @@
---
title: Integrating a New Model Architecture
description: A comprehensive guide to integrating new foundational model architectures into InvokeAI.
lastUpdated: 2026-02-19
---
import { Steps, FileTree } from '@astrojs/starlight/components';
This guide walks you through the end-to-end process of integrating a **new foundational model architecture** into InvokeAI. This is required when adding a completely new family of models (e.g., Stable Diffusion 3, FLUX, Hunyuan, etc.), rather than just adding a new checkpoint for an existing architecture.
:::note
The code examples in this guide use a hypothetical `NewModel` architecture. The implementations of `FLUX`, `SD3`, and `SDXL` in the InvokeAI codebase serve as excellent real-world references.
:::
## Architectural Overview
Integrating a new model touches several parts of the InvokeAI stack, from the lowest-level PyTorch inference code up to the React frontend:
1. **Taxonomy & Configuration (Backend)**: Declaring the model's existence and defining how to detect it from its weights on disk.
2. **Model Loading (Backend)**: Defining how to load the detected files into PyTorch models in memory.
3. **Sampling & Denoising (Backend)**: Implementing the core math for noise generation, scheduling, and the denoising loop.
4. **Invocations (Backend)**: Wrapping the PyTorch logic into isolated "nodes" that can be executed by InvokeAI's graph engine.
5. **Graph Building (Frontend)**: Instructing the UI on how to wire these nodes together based on user settings.
6. **State & UI (Frontend)**: Adding the necessary UI controls and state management for the new model's unique parameters.
---
## 1. Taxonomy & Defaults
The first step is to declare your model in the system's taxonomy and provide reasonable default settings.
<Steps>
1. **Add `BaseModelType`**
Update the base model taxonomy to include your new model.
```python title="invokeai/backend/model_manager/taxonomy.py" ins={7}
class BaseModelType(str, Enum):
# Existing types
StableDiffusion1 = "sd-1"
StableDiffusion2 = "sd-2"
StableDiffusionXL = "sdxl"
Flux = "flux"
NewModel = "newmodel"
```
2. **Add Variant Type (if needed)**
If your model comes in different structural variants (e.g., different parameter counts or distilled versions like `schnell` vs `dev`), define a variant enum.
```python title="invokeai/backend/model_manager/taxonomy.py"
class NewModelVariantType(str, Enum):
VariantA = "variant_a"
VariantB = "variant_b"
```
3. **Define Default Settings**
Provide default generation parameters (steps, CFG scale, etc.) for the UI to use when this model is selected.
```python title="invokeai/backend/model_manager/configs/main.py" ins={5-6}
class MainModelDefaultSettings:
@staticmethod
def from_base(base: BaseModelType, variant: AnyVariant | None = None):
match base:
case BaseModelType.NewModel:
return MainModelDefaultSettings(steps=20, cfg_scale=7.0)
```
</Steps>
:::tip[Checklist: Taxonomy]{icon="approve-check"}
- [ ] Extend `BaseModelType` enum in `taxonomy.py`
- [ ] Create variant enum if needed in `taxonomy.py`
- [ ] Update `AnyVariant` union in `taxonomy.py`
- [ ] Add default settings in `from_base()` in `configs/main.py`
:::
---
## 2. Model Configs & Detection
InvokeAI needs to know how to identify your model from a `.safetensors` file or a diffusers folder.
<Steps>
1. **Create Main Model Config**
Define the configuration schemas for your model format(s).
```python title="invokeai/backend/model_manager/configs/main.py"
# Checkpoint Format (Single File)
@ModelConfigFactory.register
class Main_Checkpoint_NewModel_Config(Checkpoint_Config_Base):
type: Literal[ModelType.Main] = ModelType.Main
base: Literal[BaseModelType.NewModel] = BaseModelType.NewModel
format: Literal[ModelFormat.Checkpoint] = ModelFormat.Checkpoint
variant: NewModelVariantType = NewModelVariantType.VariantA
@classmethod
def from_model_on_disk(cls, mod: ModelOnDisk, override_fields: dict) -> Self:
if not cls._validate_is_newmodel(mod):
raise NotAMatchError("Not a NewModel")
variant = cls._get_variant_or_raise(mod)
return cls(..., variant=variant)
# Diffusers Format (Folder)
@ModelConfigFactory.register
class Main_Diffusers_NewModel_Config(Diffusers_Config_Base):
type: Literal[ModelType.Main] = ModelType.Main
base: Literal[BaseModelType.NewModel] = BaseModelType.NewModel
format: Literal[ModelFormat.Diffusers] = ModelFormat.Diffusers
```
2. **Implement Detection Logic**
Write helper functions to inspect the state dictionary keys and shape to uniquely identify your architecture.
```python title="invokeai/backend/model_manager/configs/main.py"
def _is_newmodel(state_dict: dict) -> bool:
"""Detect if state dict belongs to NewModel architecture."""
# Example: check for a highly specific layer name or shape
required_keys = ["transformer_blocks.0.attn.to_q.weight"]
return all(key in state_dict for key in required_keys)
def _get_newmodel_variant(state_dict: dict) -> NewModelVariantType:
"""Determine variant from state dict."""
# Example: distinguish variants based on hidden dimension size
context_dim = state_dict["context_embedder.weight"].shape[1]
if context_dim == 7680:
return NewModelVariantType.VariantA
return NewModelVariantType.VariantB
```
3. **Submodels (VAE & Text Encoder)**
If your model uses a novel VAE or Text Encoder not already in InvokeAI, you must repeat this process to create configs for them (e.g., in `configs/vae.py` and `configs/[encoder_type].py`).
4. **Update the Configuration Union**
Register your new configs so the application knows to check them when scanning directories.
```python title="invokeai/backend/model_manager/configs/factory.py" ins={4-5}
AnyModelConfig = Annotated[
# ... existing configs
Main_Checkpoint_NewModel_Config |
Main_Diffusers_NewModel_Config,
Discriminator(...)
]
```
</Steps>
:::tip[Checklist: Configs]{icon="approve-check"}
- [ ] Create main checkpoint config (`configs/main.py`)
- [ ] Create main diffusers config (`configs/main.py`)
- [ ] Create detection helper functions (`_is_newmodel()`, `_get_variant()`)
- [ ] Create VAE and Text Encoder configs if they use novel architectures
- [ ] Update `AnyModelConfig` union (`configs/factory.py`)
:::
---
## 3. Model Loaders
Loaders are responsible for converting the files on disk (described by the config) into PyTorch models in memory.
<Steps>
1. **Create the Model Loader**
```python title="invokeai/backend/model_manager/load/model_loaders/[newmodel].py"
@ModelLoaderRegistry.register(
base=BaseModelType.NewModel,
type=ModelType.Main,
format=ModelFormat.Checkpoint
)
class NewModelLoader(ModelLoader):
def _load_model(self, config: AnyModelConfig, submodel_type: SubModelType | None) -> AnyModel:
# 1. Load the raw weights from disk
state_dict = self._load_state_dict(config.path)
# 2. Convert state dict keys if necessary (e.g. from original repo format to Diffusers)
if self._is_original_format(state_dict):
state_dict = self._convert_to_diffusers_format(state_dict)
# 3. Instantiate the empty PyTorch model
model = NewModelTransformer(config=model_config)
# 4. Load weights into the model
model.load_state_dict(state_dict)
return model
```
2. **Custom VAE/Encoder Loaders (If Applicable)**
If you created custom configs for the VAE or Text Encoder, you must also create loaders for them, registering them with the appropriate `ModelType`.
</Steps>
:::tip[Checklist: Loaders]{icon="approve-check"}
- [ ] Create and register the main model loader
- [ ] Create VAE/Encoder loaders if necessary
- [ ] Implement state dict conversion if supporting non-diffusers formats
:::
---
## 4. Sampling and Denoising Core
This is where the actual mathematical implementation of the model lives.
<Steps>
1. **Sampling Utilities**
Create utility functions specific to how your model handles noise, packing, and scheduling.
```python title="invokeai/backend/[newmodel]/sampling_utils.py"
def get_noise_newmodel(num_samples: int, height: int, width: int, seed: int, device: torch.device, dtype: torch.dtype) -> torch.Tensor:
# Models often have different latent channel counts (e.g., SD1.5 has 4, FLUX has 16)
latent_channels = 32
latent_h, latent_w = height // 8, width // 8
generator = torch.Generator(device=device).manual_seed(seed)
return torch.randn((num_samples, latent_channels, latent_h, latent_w), generator=generator, device=device, dtype=dtype)
def pack_newmodel(x: torch.Tensor) -> torch.Tensor:
# Some transformer-based models require packing latents into a sequence
return rearrange(x, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=2, pw=2)
```
If the architecture supports external noise, prefer extending the standard
`invokeai/app/invocations/noise.py` node's `noise_type` selector instead of
adding a brand new noise node. Only add a dedicated noise invocation when the
architecture's noise tensor rank or layout cannot be expressed by the
standard node.
2. **The Denoising Loop**
Implement the core sampling loop. This interacts with schedulers and handles classifier-free guidance (CFG).
```python title="invokeai/backend/[newmodel]/denoise.py"
def denoise(model: nn.Module, img: torch.Tensor, txt: torch.Tensor, timesteps: list[float], cfg_scale: list[float], scheduler: Any = None) -> torch.Tensor:
"""Main denoising loop."""
total_steps = len(timesteps) - 1
for step_index in range(total_steps):
t_curr = timesteps[step_index]
# Handle CFG (Classifier-Free Guidance)
if cfg_scale[step_index] > 1.0:
# Batch positive and negative prompts if applicable
pred_pos = model(img, t_curr, txt)
# ...
else:
pred = model(img, t_curr, txt)
# Step the scheduler
img = scheduler.step(pred, t_curr, img).prev_sample
return img
```
3. **Schedulers**
If your model requires a novel scheduler, add it to the scheduler mapping (e.g., `invokeai/backend/[newmodel]/schedulers.py`).
</Steps>
:::tip[Checklist: Core Inference]{icon="approve-check"}
- [ ] Noise generation (`get_noise_newmodel()`)
- [ ] Pack/unpack functions (if transformer-based)
- [ ] Timestep schedule generation
- [ ] Denoise loop implementation
- [ ] Map supported schedulers
:::
---
## 5. Invocations
Invocations expose your PyTorch functions as isolated execution nodes in InvokeAI's graph.
<Steps>
1. **Model Loader Invocation**
Loads the components (Transformer, VAE, etc.) and provides them to downstream nodes.
```python title="invokeai/app/invocations/[newmodel]_model_loader.py"
@invocation("newmodel_model_loader", title="NewModel Loader", category="model_loader")
class NewModelModelLoaderInvocation(BaseInvocation):
model: ModelIdentifierField = InputField(description="Main model")
def invoke(self, context: InvocationContext) -> NewModelLoaderOutput:
transformer = self.model.model_copy(update={"submodel_type": SubModelType.Transformer})
vae = self.model.model_copy(update={"submodel_type": SubModelType.VAE})
return NewModelLoaderOutput(transformer=transformer, vae=vae)
```
2. **Text Encoder Invocation**
Tokenizes the prompt and runs the text encoder(s).
```python title="invokeai/app/invocations/[newmodel]_text_encoder.py"
@invocation("newmodel_text_encode", title="NewModel Text Encoder", category="conditioning")
class NewModelTextEncoderInvocation(BaseInvocation):
prompt: str = InputField()
encoder: EncoderField = InputField()
def invoke(self, context: InvocationContext) -> ConditioningOutput:
# 1. Tokenize prompt
# 2. Run encoder to get embeddings
# 3. Save to context and return
conditioning_name = context.conditioning.save(ConditioningFieldData(...))
return ConditioningOutput(conditioning=ConditioningField(conditioning_name=conditioning_name))
```
3. **Denoise Invocation**
Wraps the `denoise` loop you wrote in the previous section.
```python title="invokeai/app/invocations/[newmodel]_denoise.py"
@invocation("newmodel_denoise", title="NewModel Denoise", category="latents")
class NewModelDenoiseInvocation(BaseInvocation):
latents: LatentsField | None = InputField(default=None)
noise: LatentsField | None = InputField(default=None)
positive_conditioning: ConditioningField = InputField()
transformer: TransformerField = InputField()
steps: int = InputField(default=20)
cfg_scale: float = InputField(default=7.0)
def invoke(self, context: InvocationContext) -> LatentsOutput:
# Generate noise, get schedule, and call your denoise() function
pass
```
If you add external noise support, keep it optional so seed-driven workflows
continue to work. Validate connected noise against the architecture's
expected shape before using it.
4. **VAE Encode / Decode Invocations**
Create nodes to transition between pixel space (images) and latent space.
</Steps>
:::tip[Checklist: Invocations]{icon="approve-check"}
- [ ] Define output classes (e.g., `NewModelLoaderOutput`)
- [ ] Model loader invocation (`[newmodel]_model_loader.py`)
- [ ] Text encoder invocation (`[newmodel]_text_encoder.py`)
- [ ] Denoise invocation (`[newmodel]_denoise.py`)
- [ ] Extend the standard `noise` invocation if the architecture supports external noise
- [ ] VAE encode/decode invocations (`[newmodel]_vae_encode.py`, `[newmodel]_vae_decode.py`)
:::
---
## 6. Frontend: Graph Building
The UI doesn't know about Python functions; it only knows how to build graphs of Invocations.
<Steps>
1. **Create the Graph Builder**
Write a TypeScript function that constructs the node graph for your model.
```typescript title="invokeai/frontend/web/src/features/nodes/util/graph/generation/buildNewModelGraph.ts"
export const buildNewModelGraph = async (arg: GraphBuilderArg): Promise<GraphBuilderResult> => {
const { state, manager } = arg;
const { model } = state.params;
const g = new Graph();
// 1. Add Loader
const modelLoader = g.addNode({
id: NEWMODEL_MODEL_LOADER,
type: 'newmodel_model_loader',
model: Graph.getModelMetadataField(model),
});
// 2. Add Text Encoders
const positivePrompt = g.addNode({
id: POSITIVE_CONDITIONING,
type: 'newmodel_text_encode',
prompt: state.params.positivePrompt,
});
g.addEdge(modelLoader, 'encoder', positivePrompt, 'encoder');
// 3. Add Denoise
const denoise = g.addNode({
id: NEWMODEL_DENOISE,
type: 'newmodel_denoise',
steps: state.params.steps,
cfg_scale: state.params.cfg,
});
g.addEdge(modelLoader, 'transformer', denoise, 'transformer');
g.addEdge(positivePrompt, 'conditioning', denoise, 'positive_conditioning');
// 4. Add VAE Decode
const l2i = g.addNode({
id: NEWMODEL_VAE_DECODE,
type: 'newmodel_vae_decode',
});
g.addEdge(modelLoader, 'vae', l2i, 'vae');
g.addEdge(denoise, 'latents', l2i, 'latents');
return { g, denoise, posCond: positivePrompt };
};
```
2. **Register the Graph Builder**
Hook your graph builder into the main routing logic.
```typescript title="invokeai/frontend/web/src/features/queue/hooks/useEnqueueCanvas.ts" ins={5-6}
switch (base) {
case 'sdxl':
return buildSDXLGraph(arg);
case 'flux':
return buildFLUXGraph(arg);
case 'newmodel':
return buildNewModelGraph(arg);
}
```
3. **Update Type Definitions**
Add your new nodes to the strict frontend type unions.
```typescript title="invokeai/frontend/web/src/features/nodes/util/graph/types.ts" ins="| 'newmodel_vae_decode'"
export type ImageOutputNodes =
| 'l2i' | 'flux_vae_decode' | 'sd3_l2i' | 'newmodel_vae_decode';
```
4. **Generation Modes**
Update `invokeai/app/invocations/metadata.py` to include your new modes in `GENERATION_MODES` (e.g., `"newmodel_txt2img"`, `"newmodel_img2img"`).
</Steps>
:::tip[Checklist: Graph Building]{icon="approve-check"}
- [ ] Create graph builder (`buildNewModelGraph.ts`)
- [ ] Register graph builder in `useEnqueueCanvas.ts`
- [ ] Update node unions in `types.ts`
- [ ] Add generation modes to python `metadata.py`
:::
---
## 7. Frontend: State & UI
Finally, add any custom UI controls (like a specific scheduler dropdown) and manage their state.
<Steps>
1. **Add to Redux State**
Update the parameters slice for your model-specific settings.
```typescript title="invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts"
interface ParamsState {
// ...
newmodelScheduler: 'euler' | 'heun';
}
const initialState: ParamsState = {
// ...
newmodelScheduler: 'euler',
};
// Add reducers and export selectors...
```
2. **Parameter Recall**
Ensure users can extract parameters from previously generated images by updating `invokeai/frontend/web/src/features/metadata/parsing.tsx`.
```typescript title="invokeai/frontend/web/src/features/metadata/parsing.tsx"
const recallNewmodelScheduler = (metadata: CoreMetadata) => {
if (metadata.scheduler) {
dispatch(setNewmodelScheduler(metadata.scheduler));
}
};
```
</Steps>
:::tip[Checklist: State & UI]{icon="approve-check"}
- [ ] Extend state interface for model-specific parameters
- [ ] Create reducers and selectors
- [ ] Add parameter recall handlers in `parsing.tsx`
:::
---
## 8. Optional Features
Depending on the model, you may want to support additional features.
### ControlNet Support
Requires backend configuration (`configs/controlnet.py`), a custom invocation (`[newmodel]_controlnet.py`), and frontend graph integration (`addControlNets`).
### LoRA Support
Requires defining a LoRA config (`configs/lora.py`), updating the model loader to pass LoRA fields, and wiring `addLoRAs` in the frontend graph builder.
### IP-Adapter
Requires a custom invocation for image prompting (`[newmodel]_ip_adapter.py`) and frontend integration via `addIPAdapters`.
---
## 9. Starter Models
To allow users to easily download your model from the Model Manager UI, add it to the starter models list.
```python title="invokeai/backend/model_manager/starter_models.py"
newmodel_main = StarterModel(
name="NewModel Main",
base=BaseModelType.NewModel,
source="organization/newmodel-main", # HuggingFace repo
description="NewModel main transformer.",
type=ModelType.Main,
)
STARTER_MODELS.append(newmodel_main)
```
:::tip[Checklist: Starter Models]{icon="approve-check"}
- [ ] Define main model StarterModel
- [ ] Define VAE/Encoder StarterModels if separate
- [ ] Set dependencies correctly if required
- [ ] Add to `STARTER_MODELS` list
:::
---
## Summary of Integration Files
A complete minimal `txt2img` integration touches the following areas:
<FileTree>
- invokeai
- app/invocations
- metadata.py
- `[newmodel]_model_loader.py`
- `[newmodel]_text_encoder.py`
- `[newmodel]_denoise.py`
- `[newmodel]_vae_decode.py`
- backend
- model_manager
- taxonomy.py
- configs
- main.py
- factory.py
- load/model_loaders
- `[newmodel].py`
- starter_models.py
- `[newmodel]`
- sampling_utils.py
- denoise.py
- frontend/web/src/features
- nodes/util/graph
- generation/buildNewModelGraph.ts
- types.ts
- queue/hooks/useEnqueueCanvas.ts
- controlLayers/store/paramsSlice.ts
- metadata/parsing.tsx
</FileTree>
@@ -0,0 +1,572 @@
---
title: Recall Parameters API
---
## Overview
The Recall Parameters API is a REST endpoint on the InvokeAI backend that
lets external processes set recallable generation parameters on the
frontend. Supported parameters include:
- Core text and numeric parameters (prompts, model, steps, CFG, dimensions, seed, ...)
- LoRAs
- Control Layers (ControlNet, T2I Adapter, Control LoRA) with optional control images
- IP Adapters and FLUX Redux reference images with optional images
- Model-free reference images (FLUX.2 Klein, FLUX Kontext, Qwen Image Edit)
When parameters are updated via the API, the backend stores them in client
state persistence for the target queue and broadcasts a `recall_parameters_updated`
WebSocket event. Any frontend client subscribed to that queue applies the
new values immediately — no manual reload required.
Typical use cases:
- An external image browser that wants to "recall" or "remix" the
generation parameters saved into a PNG's metadata.
- A script that pre-populates parameters before the user runs generation.
- Automated testing or batch workflows that want to reuse existing model
and adapter configurations.
## How It Works
1. **API request** — your client POSTs a JSON body of parameters to
`/api/v1/recall/{queue_id}`.
2. **Storage** — non-null parameters are stored under
`recall_*` keys in the client state persistence service, scoped to the
given `queue_id`.
3. **Resolution** — models are resolved from human-readable names to the
internal model keys used by the frontend, and image filenames are
validated against `{INVOKEAI_ROOT}/outputs/images`.
4. **Broadcast** — a `recall_parameters_updated` event is emitted on the
websocket room for `queue_id`.
5. **Frontend update** — any connected client subscribed to that queue
applies the update to its Redux store, so UI fields, LoRAs, control
layers, IP adapters, and reference images all populate immediately.
## Endpoint
**Base URL:** `http://localhost:9090/api/v1/recall/{queue_id}`
The queue id is usually `default`.
### POST — Update Recall Parameters
Updates recallable parameters for the given `queue_id`.
```http
POST /api/v1/recall/{queue_id}
Content-Type: application/json
{
"positive_prompt": "a beautiful landscape",
"negative_prompt": "blurry, low quality",
"model": "sd-1.5",
"steps": 20,
"cfg_scale": 7.5,
"width": 512,
"height": 512,
"seed": 12345
}
```
All parameters are optional — only send the fields you want to update.
#### Query parameters
The POST endpoint accepts two optional boolean query parameters that control
how reference images are merged into the frontend state:
| Parameter | Default | Description |
|-----------|---------|-------------|
| `strict` | `false` | When `true`, parameters **not** included in the request body are reset to their defaults (cleared on the frontend). When `false`, only the parameters you send are updated and everything else is left as-is. |
| `append` | `false` | When `true`, recalled reference images (`ip_adapters` and `reference_images`) are **appended** to the frontend's existing reference-image list instead of replacing it. When `false` (or omitted), the recalled reference images **replace** the existing list. |
`strict` and `append` are mutually exclusive — `strict` clears omitted
parameters while `append` preserves and extends the existing list, so the two
cannot be combined. Sending `?strict=true&append=true` returns
**400 Bad Request**:
```json
{
"detail": "The 'strict' and 'append' query parameters are mutually exclusive"
}
```
`append` only affects the reference-image collections (`ip_adapters` and
`reference_images`). All other parameters (prompts, model, LoRAs, control
layers, etc.) are updated the same way regardless of the flag.
### GET — Retrieve Recall Parameters
```http
GET /api/v1/recall/{queue_id}
```
```json
{
"status": "success",
"queue_id": "queue_123",
"note": "Use the frontend to access stored recall parameters, or set specific parameters using POST"
}
```
## Request Schema
### Core parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `positive_prompt` | string | Positive prompt text |
| `negative_prompt` | string | Negative prompt text |
| `model` | string | Main model name/identifier |
| `refiner_model` | string | Refiner model name/identifier |
| `vae_model` | string | VAE model name/identifier |
| `scheduler` | string | Scheduler name |
| `steps` | integer | Number of generation steps (≥1) |
| `refiner_steps` | integer | Number of refiner steps (≥0) |
| `cfg_scale` | number | CFG scale for guidance |
| `cfg_rescale_multiplier` | number | CFG rescale multiplier |
| `refiner_cfg_scale` | number | Refiner CFG scale |
| `guidance` | number | Guidance scale |
| `width` | integer | Image width in pixels (≥64) |
| `height` | integer | Image height in pixels (≥64) |
| `seed` | integer | Random seed (≥0) |
| `denoise_strength` | number | Denoising strength (01) |
| `refiner_denoise_start` | number | Refiner denoising start (01) |
| `clip_skip` | integer | CLIP skip layers (≥0) |
| `seamless_x` | boolean | Enable seamless X tiling |
| `seamless_y` | boolean | Enable seamless Y tiling |
| `refiner_positive_aesthetic_score` | number | Refiner positive aesthetic score |
| `refiner_negative_aesthetic_score` | number | Refiner negative aesthetic score |
### Collection parameters
```typescript
{
// LoRAs
loras?: Array<{
model_name: string; // LoRA model name
weight?: number; // Default: 0.75, Range: -10 to 10
is_enabled?: boolean; // Default: true
}>;
// Control Layers (ControlNet, T2I Adapter, Control LoRA)
control_layers?: Array<{
model_name: string; // Control adapter model name
image_name?: string; // Optional image filename from outputs/images
weight?: number; // Default: 1.0, Range: -1 to 2
begin_step_percent?: number; // Default: 0.0, Range: 0 to 1
end_step_percent?: number; // Default: 1.0, Range: 0 to 1
control_mode?: "balanced" | "more_prompt" | "more_control"; // ControlNet only
}>;
// IP Adapters (includes FLUX Redux)
ip_adapters?: Array<{
model_name: string; // IP Adapter / FLUX Redux model name
image_name?: string; // Optional reference image filename from outputs/images
weight?: number; // Default: 1.0, Range: -1 to 2
begin_step_percent?: number; // Default: 0.0, Range: 0 to 1
end_step_percent?: number; // Default: 1.0, Range: 0 to 1
method?: "full" | "style" | "composition"; // Default: "full"
image_influence?: "lowest" | "low" | "medium" | "high" | "highest"; // FLUX Redux only
}>;
// Model-free reference images (FLUX.2 Klein, FLUX Kontext, Qwen Image Edit)
reference_images?: Array<{
image_name: string; // Reference image filename from outputs/images
}>;
}
```
## Model Name Resolution
The backend resolves model names to their internal keys:
1. **Main models** — resolved from the name to the model key.
2. **LoRAs** — searched in the LoRA model database.
3. **Control adapters** — tried in order: ControlNet → T2I Adapter → Control LoRA.
4. **IP Adapters** — searched in the IP Adapter database; falls back to FLUX Redux.
Models that cannot be resolved are skipped with a warning in the logs —
the rest of the parameters are still applied.
## Image File Handling
When an `image_name` is supplied, the backend:
1. Resolves `{INVOKEAI_ROOT}/outputs/images/{image_name}` via the image
files service (which also validates the path).
2. Opens the image to extract width/height.
3. Includes the image metadata in the event sent to the frontend.
4. Logs whether the image was found.
Images must be referenced by their filename as it appears in the
outputs/images directory:
- ✅ `"image_name": "example.png"`
- ✅ `"image_name": "my_control_image_20240110.jpg"`
- ❌ `"image_name": "outputs/images/example.png"` (no prefix)
- ❌ `"image_name": "/full/path/to/example.png"` (no absolute paths)
Missing images are logged as warnings but **do not** fail the request —
remaining parameters are still applied.
## Feature Details
### LoRAs
- Existing LoRAs are cleared before new ones are added.
- Each LoRA's model config is fetched and applied with the specified weight.
- LoRAs appear in the LoRA selector panel.
### Control Layers
- Fully supported with optional images from `outputs/images`.
- Configuration includes model, weights, step percentages, control mode,
and an image reference.
- Image availability is logged in the frontend console.
### IP Adapters / FLUX Redux
- Reference images loaded from `outputs/images` are validated and passed
through.
- Configuration includes model, weights, step percentages, method, and an
image reference.
- FLUX Redux uses `image_influence` instead of a numeric weight.
### Model-free reference images
Used by architectures that consume a reference image directly, with no
separate adapter model:
- **FLUX.2 Klein** — built-in reference image support.
- **FLUX Kontext** — reference image associated with the main model.
- **Qwen Image Edit** — reference image associated with the main model.
Because there is no adapter model to resolve, these entries carry only
`image_name`. When the frontend receives them, it picks the appropriate
config flavor (`flux2_reference_image`, `flux_kontext_reference_image`,
or `qwen_image_reference_image`) based on the currently-selected main
model, matching the behavior of a manual drag-and-drop.
## Usage Examples
### cURL
```bash
# Core parameters
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{
"positive_prompt": "a cyberpunk city at night",
"negative_prompt": "dark, unclear",
"model": "sd-1.5",
"steps": 30
}'
# Just the seed
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{"seed": 99999}'
```
### LoRAs only
```bash
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{
"loras": [
{"model_name": "add-detail-xl", "weight": 0.8, "is_enabled": true},
{"model_name": "sd_xl_offset_example-lora_1.0", "weight": 0.5}
]
}'
```
### Control layers with an image
```bash
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{
"control_layers": [
{
"model_name": "controlnet-canny-sdxl-1.0",
"image_name": "my_control_image.png",
"weight": 0.75,
"begin_step_percent": 0.0,
"end_step_percent": 0.8,
"control_mode": "balanced"
}
]
}'
```
### IP adapters with a reference image
```bash
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{
"ip_adapters": [
{
"model_name": "ip-adapter-plus-face_sd15",
"image_name": "reference_face.png",
"weight": 0.7,
"method": "composition"
}
]
}'
```
### Appending reference images (append mode)
By default, recalled reference images **replace** whatever the frontend
already has. Pass `?append=true` to **add** the recalled `ip_adapters` and
`reference_images` to the existing list instead:
```bash
# Add a reference image without clearing the ones already on the frontend
curl -X POST 'http://localhost:9090/api/v1/recall/default?append=true' \
-H "Content-Type: application/json" \
-d '{
"reference_images": [
{"image_name": "extra_reference.png"}
]
}'
```
Combining `append=true` with `strict=true` is invalid and returns
**400 Bad Request** (see [Query parameters](#query-parameters)).
### Model-free reference images (FLUX.2 Klein / FLUX Kontext / Qwen Image Edit)
```bash
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{
"model": "FLUX.2 Klein",
"reference_images": [
{"image_name": "style_reference.png"}
]
}'
```
### Complete configuration
```bash
curl -X POST http://localhost:9090/api/v1/recall/default \
-H "Content-Type: application/json" \
-d '{
"positive_prompt": "masterpiece, detailed photo with specific style",
"negative_prompt": "blurry, low quality",
"model": "FLUX Schnell",
"steps": 25,
"cfg_scale": 8.0,
"width": 1024,
"height": 768,
"seed": 42,
"loras": [
{"model_name": "add-detail-xl", "weight": 0.6}
],
"control_layers": [
{
"model_name": "controlnet-depth-sdxl-1.0",
"image_name": "depth_map.png",
"weight": 1.0,
"end_step_percent": 0.7
}
],
"ip_adapters": [
{
"model_name": "ip-adapter-plus-face_sd15",
"image_name": "style_reference.png",
"weight": 0.5,
"method": "style"
}
]
}'
```
### Python
```python
import requests
API_URL = "http://localhost:9090/api/v1/recall/default"
params = {
"positive_prompt": "a serene forest",
"negative_prompt": "people, buildings",
"steps": 25,
"cfg_scale": 7.0,
"seed": 42,
}
response = requests.post(API_URL, json=params)
result = response.json()
print(f"Status: {result['status']}")
print(f"Updated {result['updated_count']} parameters")
```
### JavaScript
```javascript
const API_URL = 'http://localhost:9090/api/v1/recall/default';
fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
positive_prompt: 'a beautiful sunset',
steps: 20,
width: 768,
height: 768,
seed: 12345,
}),
})
.then((res) => res.json())
.then((data) => console.log(data));
```
## Response Format
```json
{
"status": "success",
"queue_id": "default",
"updated_count": 15,
"parameters": {
"positive_prompt": "...",
"steps": 25,
"loras": [
{"model_key": "abc123...", "weight": 0.6, "is_enabled": true}
],
"control_layers": [
{
"model_key": "controlnet-xyz...",
"weight": 1.0,
"image": {"image_name": "depth_map.png", "width": 1024, "height": 768}
}
],
"ip_adapters": [
{
"model_key": "ip-adapter-xyz...",
"weight": 0.5,
"image": {"image_name": "style_reference.png", "width": 1024, "height": 1024}
}
],
"reference_images": [
{"image": {"image_name": "style_reference.png", "width": 1024, "height": 1024}}
]
}
}
```
## WebSocket Events
Parameter updates emit a `recall_parameters_updated` event to the queue
room. Connected frontend clients automatically:
1. Apply standard parameters (prompts, steps, dimensions, etc.).
2. Load and add LoRAs to the LoRA list.
3. Apply control-layer configurations.
4. Merge the recalled reference images — IP Adapter / FLUX Redux entries and
model-free reference images both feed the same reference-image list, using
the config flavor that matches the currently-selected main model. By
default this **replaces** the existing list; with `append=true` it is
**added** to whatever is already there (see
[Query parameters](#query-parameters)).
## Error Handling
- **400 Bad Request** — invalid parameters or parameter values, or the
mutually exclusive `strict=true&append=true` combination (see
[Query parameters](#query-parameters)).
- **500 Internal Server Error** — server-side storage or retrieval failure.
Errors include detailed messages. Missing images and unresolved model
names are **not** errors — they are logged and the remaining parameters
are still applied.
## Logging
### Backend
```
INFO: Resolved ControlNet model name 'controlnet-canny-sdxl-1.0' to key 'controlnet-xyz...'
INFO: Found image file: depth_map.png (1024x768)
INFO: Updated 12 recall parameters for queue default
INFO: Resolved 1 LoRA(s)
INFO: Resolved 1 control layer(s)
INFO: Resolved 1 IP adapter(s)
INFO: Resolved 1 reference image(s)
```
### Frontend
Set `localStorage.ROARR_FILTER = 'debug'` in the browser to see all debug
messages under the `events` namespace.
```
INFO: Applied 5 recall parameters to store
INFO: Applied 2 reference image(s) (IP adapters + model-free), replacing existing list
DEBUG: Built IP adapter ref image state: ip-adapter-xyz... (weight: 0.7)
DEBUG: IP adapter image: outputs/images/depth_map.png (1024x768)
```
## Implementation Details
- Parameters are stored in the client state persistence service under
`recall_*` keys, scoped to the `queue_id`.
- Numeric validation runs at the FastAPI layer (e.g. `steps ≥ 1`, `width ≥ 64`).
- Only non-null parameters are processed, stored, and broadcast.
- Model-key resolution runs **after** the raw parameters are stored, so
an unresolvable model name simply drops out of the broadcast but does
not corrupt the persisted state.
- The broadcast payload contains resolved model keys and image metadata
(width/height) so the frontend can populate its store without extra
round-trips.
## Troubleshooting
### Image not found
If you see "Image file not found" in the logs:
1. Verify the filename matches exactly (case-sensitive).
2. Ensure the image is in `{INVOKEAI_ROOT}/outputs/images/`.
3. Check that the filename does not include the `outputs/images/` prefix.
### Model not found
If you see "Could not find model":
1. Verify the model name matches exactly (case-sensitive).
2. Ensure the model is installed.
3. Check the name via the Models Manager panel.
### Event not received
1. Check the browser console for socket connection errors.
2. Verify the `queue_id` matches the frontend's queue (usually `default`).
3. Check backend logs for event emission errors.
## Limitations
- **Model availability** — models referenced in the payload must be installed.
- **Image availability** — images must exist in `outputs/images`; remote
URLs are not supported.
- **Canvas auto-layer creation** — control layers and IP adapters with
images populate the recall state, but creating a canvas layer from
them still happens through the UI.
## Future enhancements
Potential improvements not yet implemented:
1. Auto-create canvas layers from control-layer images in the payload.
2. Auto-create reference-image layers from IP Adapter images in the payload.
3. Support remote image URLs in addition to local `outputs/images` filenames.
4. Image upload capability (accept base64 or file upload directly via the API).
5. Batch operations that target multiple `queue_id`s in a single request.
@@ -0,0 +1,170 @@
---
title: SQLite Database Migrations
lastUpdated: 2026-06-30
---
InvokeAI uses a custom SQLite migrator for the main application database. Migrations live in `invokeai/app/services/shared/sqlite_migrator/migrations/` and are discovered automatically when the database is initialized.
Use a migration when a change modifies persisted database schema or persisted database data in a way that existing installs must receive on startup.
## Naming
New migration modules should use a date-stamped descriptive name:
```text
invokeai/app/services/shared/sqlite_migrator/migrations/migration_2026_06_30_add_example_table.py
```
Date-stamped migration modules must expose a `build_migration()` builder:
```py
def build_migration() -> Migration:
...
```
The date prefix should be the date the migration is authored or merged, in `YYYY_MM_DD` format. Add a short snake_case description after the date.
Legacy numeric migration modules are still supported:
```text
invokeai/app/services/shared/sqlite_migrator/migrations/migration_33.py
```
Numeric migration modules must expose the matching legacy builder:
```py
def build_migration_33() -> Migration:
...
```
The loader discovers both naming styles. Numeric modules are imported in numeric order for legacy compatibility. Date-stamped modules are imported in lexical order, but dependency metadata drives execution order. Non-matching migration module names, missing builders, unknown builder dependencies, and builders that do not return `Migration` fail at startup.
## IDs and Dependencies
Every migration has a stable `id`. For new date-stamped migrations, use the module name without the `migration_` prefix:
```py
id="2026_06_30_add_example_table"
```
The loader enforces this match. If `migration_2026_06_30_add_example_table.py` returns a different ID, startup fails instead of persisting a typo that could later cause the migration to run again.
Existing numeric migrations use IDs matching their file number:
```py
id="migration_33"
```
Legacy numeric migrations that define `from_version` and `to_version` may only depend on other legacy numeric migrations. New migrations should usually be date-stamped graph-only migrations, and should not define legacy versions unless a specific legacy compatibility requirement exists.
For a migration that only needs an older schema point, depend on the migration that introduced the required schema or data:
```py
depends_on="migration_27"
```
New production migrations should always set `depends_on`. Only a true root migration should use `depends_on=None`. A migration with no dependency is considered independently runnable, so omitting `depends_on` for a normal schema or data change can allow it to run before the schema it expects exists.
Dependencies drive execution order. If two migrations both depend on `migration_27`, either may run after `migration_27` unless one explicitly depends on the other. Do not rely on filename or lexical ordering to express a real dependency.
Single-parent dependencies are currently supported. If a migration truly requires two independent branches, add a real dependency between those branches only when that ordering is semantically correct. Otherwise, update the migrator design and tests to support multiple dependencies.
## Template
```py
import sqlite3
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
class AddExampleTableCallback:
def __call__(self, cursor: sqlite3.Cursor) -> None:
cursor.execute("ALTER TABLE images ADD COLUMN example TEXT;")
def build_migration() -> Migration:
return Migration(
id="2026_06_30_add_example_table",
depends_on="migration_32",
callback=AddExampleTableCallback(),
)
```
Keep the callback self-contained. It receives an open SQLite cursor and must not commit or roll back the transaction. The migrator commits the migration and records it as applied only after the callback succeeds. If the callback raises, the migration transaction is rolled back and the migration is not recorded.
## Builder Dependencies
Migration builders may request known application dependencies by parameter name. The loader inspects the builder signature and passes only the dependencies requested.
Supported dependency names:
- `app_config` or `config`
- `logger`
- `image_files`
Example:
```py
def build_migration(app_config: InvokeAIAppConfig, logger: Logger) -> Migration:
return Migration(
id="2026_06_30_normalize_model_paths",
depends_on="2026_06_30_add_example_table",
callback=NormalizeModelPathsCallback(app_config=app_config, logger=logger),
)
```
Do not use `*args`, `**kwargs`, or positional-only parameters in migration builders.
## Registration
Do not manually import or register migrations in `sqlite_util.py`.
Database initialization builds a `MigrationBuildContext`, discovers migration modules, calls their builders, and registers the resulting `Migration` objects with `SqliteMigrator`.
Manual registration is still available for tests:
```py
migrator.register_migration(Migration(...))
```
## Applied State and Compatibility
The migrator records stable migration IDs in the `applied_migrations` table. Legacy numeric versions are still written to the existing `migrations` table for migrations that define `to_version`.
New date-stamped migrations should not define `from_version` or `to_version` unless there is a specific legacy compatibility reason. Use `id` and `depends_on` to define execution.
Existing databases are bootstrapped from legacy numeric rows:
- legacy version `1` maps to `migration_1`
- legacy version `2` maps to `migration_2`
- and so on
If a database contains an applied migration ID or legacy numeric version unknown to the current code, startup fails before running migrations. This prevents older code from running against a newer schema. Downgrading to an InvokeAI version that does not know about migrations already applied by a newer version is not supported.
For legacy migrations, the two metadata tables must agree. For example, `applied_migrations` may only record `migration_2` with `legacy_version = 2` if the legacy `migrations` table also contains version `2`. If these records are inconsistent, startup fails before user migration callbacks run.
When opening a file-backed legacy database for the first time after this migration system change, the migrator may need to create and populate `applied_migrations` even if no user migration callbacks need to run. This metadata bootstrap is backed up before the new metadata table is written.
## Tests
Add focused tests for each migration callback under:
```text
tests/app/services/shared/sqlite_migrator/migrations/
```
At minimum, cover:
- the schema or data change performed by the callback
- idempotent or already-migrated behavior when relevant
- missing optional source tables or columns when the migration is expected to tolerate them
- the builder's `id` and `depends_on`
- legacy `from_version` and `to_version`, if the migration defines them
Also run the migrator tests:
```bash
pytest tests/app/services/shared/sqlite_migrator tests/test_sqlite_migrator.py
```
These tests use in-memory databases or pytest temporary directories. They should not touch a real InvokeAI database.
@@ -0,0 +1,102 @@
---
title: Writing Tests
lastUpdated: 2026-02-20
---
## Frontend Tests
We use `vitest` to run the frontend tests. (See [vite.config.ts](https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/vite.config.mts) for the default `vitest` options.)
{/* TODO: Finish frontend tests docs */}
## Backend Tests
We use `pytest` to run the backend python tests. (See [pyproject.toml](https://github.com/invoke-ai/InvokeAI/blob/main/pyproject.toml) for the default `pytest` options.)
### Fast vs. Slow
All tests are categorized as either 'fast' (no test annotation) or 'slow' (annotated with the `@pytest.mark.slow` decorator).
'Fast' tests are run to validate every PR, and are fast enough that they can be run routinely during development.
'Slow' tests are currently only run manually on an ad-hoc basis. In the future, they may be automated to run nightly. Most developers are only expected to run the 'slow' tests that directly relate to the feature(s) that they are working on.
As a rule of thumb, tests should be marked as 'slow' if there is a chance that they take >1s (e.g. on a CPU-only machine with slow internet connection). Common examples of slow tests are tests that depend on downloading a model, or running model inference.
### Running Tests
Below are some common test commands:
```bash
# Run the fast tests. (This implicitly uses the configured default option: `-m "not slow"`.)
pytest tests/
# Equivalent command to run the fast tests.
pytest tests/ -m "not slow"
# Run the slow tests.
pytest tests/ -m "slow"
# Run the slow tests from a specific file.
pytest tests/path/to/slow_test.py -m "slow"
# Run all tests (fast and slow).
pytest tests -m ""
```
### Test Organization
All backend tests are in the [`tests/`](https://github.com/invoke-ai/InvokeAI/tree/main/tests) directory. This directory mirrors the organization of the `invokeai/` directory. For example, tests for `invokeai/model_management/model_manager.py` would be found in `tests/model_management/test_model_manager.py`.
TODO: The above statement is aspirational. A re-organization of legacy tests is required to make it true.
### Tests that depend on models
There are a few things to keep in mind when adding tests that depend on models.
1. If a required model is not already present, it should automatically be downloaded as part of the test setup.
2. If a model is already downloaded, it should not be re-downloaded unnecessarily.
3. Take reasonable care to keep the total number of models required for the tests low. Whenever possible, re-use models that are already required for other tests. If you are adding a new model, consider including a comment to explain why it is required/unique.
There are several utilities to help with model setup for tests. Here is a sample test that depends on a model:
```python
import pytest
import torch
from invokeai.backend.model_management.models.base import BaseModelType, ModelType
from invokeai.backend.util.test_utils import install_and_load_model
@pytest.mark.slow
def test_model(model_installer, torch_device):
model_info = install_and_load_model(
model_installer=model_installer,
model_path_id_or_url="HF/dummy_model_id",
model_name="dummy_model",
base_model=BaseModelType.StableDiffusion1,
model_type=ModelType.Dummy,
)
dummy_input = build_dummy_input(torch_device)
with torch.no_grad(), model_info as model:
model.to(torch_device, dtype=torch.float32)
output = model(dummy_input)
# Validate output...
```
### Test Coverage
To review test coverage, append `--cov` to your pytest command:
```bash
pytest tests/ --cov
```
Test outcomes and coverage will be reported in the terminal. In addition, a more detailed report is created in both XML and HTML format in the `./coverage` folder. The HTML output is particularly helpful in identifying untested statements where coverage should be improved. The HTML report can be viewed by opening `./coverage/html/index.html`.
:::note HTML coverage report output example
![html-overview](./assets/html-overview.png)
![html-detail](./assets/html-detail.png)
:::
@@ -0,0 +1,106 @@
---
title: Workflow Execution API
---
## Overview
InvokeAI's HTTP API can be used programmatically from external clients, but one distinction is easy to miss:
- A saved **workflow** is not executed directly.
- The queue accepts an executable **graph**.
- If you fetch a saved workflow from `/api/v1/workflows/`, you still need to enqueue a graph with `POST /api/v1/queue/default/enqueue_batch`.
This is a common source of confusion when a client can successfully read workflow records but cannot execute them.
If you are using multi-user mode, include your `Authorization: Bearer <token>` header on these requests.
## Minimal `enqueue_batch` Example
The smallest useful "hello world" is a simple graph with one math node. This avoids any image-model setup and lets you verify that your client can enqueue work and poll for completion.
```python
import requests
import time
BASE_URL = "http://localhost:9090"
graph = {
"id": "hello-graph",
"nodes": {
"add-node": {
"id": "add-node",
"type": "add",
"a": 2,
"b": 3,
"is_intermediate": False,
"use_cache": True,
}
},
"edges": [],
}
payload = {
"batch": {
"graph": graph,
"runs": 1,
"origin": "external-client",
}
}
enqueue_response = requests.post(
f"{BASE_URL}/api/v1/queue/default/enqueue_batch",
json=payload,
)
enqueue_response.raise_for_status()
item_id = enqueue_response.json()["item_ids"][0]
while True:
item_response = requests.get(f"{BASE_URL}/api/v1/queue/default/i/{item_id}")
item_response.raise_for_status()
item = item_response.json()
status = item["status"]
if status == "completed":
print(item["session"]["results"])
break
if status == "failed":
raise RuntimeError(item["error_message"])
if status == "canceled":
raise RuntimeError("Queue item was canceled")
time.sleep(0.5)
```
For the graph above, the completed queue item will contain the output in `session.results`.
## Getting an Image Output
For image-generation graphs, the completed queue item also includes `session.results`, but the output object will typically contain an image reference instead of a plain integer value.
For example, an image output may look like this:
```json
{
"type": "image_output",
"image": {
"image_name": "abc123.png"
}
}
```
Once you have the `image_name`, you can download the generated file from:
```text
GET /api/v1/images/i/{image_name}/full
```
If you only need the file preview, you can also use:
```text
GET /api/v1/images/i/{image_name}/thumbnail
```
## Polling vs. WebSockets
The example above uses polling because it is the easiest way to get started from an external client. If you need lower-latency updates, you can also use the socket events emitted during queue execution.
@@ -0,0 +1,72 @@
---
title: PR Merge Policy
lastUpdated: 2026-02-19
---
import { Steps } from '@astrojs/starlight/components';
This document outlines the process for reviewing and merging pull requests (PRs) into the InvokeAI repository.
## Review Process
<Steps>
1. Assignment
One of the repository maintainers will assign collaborators to review a pull request. The assigned reviewer(s) will be responsible for conducting the code review.
2. Review and Iteration
The assignee is responsible for:
- Reviewing the PR thoroughly
- Providing constructive feedback
- Iterating with the PR author until the assignee is satisfied that the PR is fit to merge
- Ensuring the PR meets code quality standards, follows project conventions, and doesn't introduce bugs or regressions
3. Approval and Notification
Once the assignee is satisfied with the PR:
- The assignee approves the PR
- The assignee alerts one of the maintainers that the PR is ready for merge using the **#request-reviews Discord channel**
4. Final Merge
One of the maintainers is responsible for:
- Performing a final check of the PR
- Merging the PR into the appropriate branch
:::caution[Important]
Collaborators are strongly discouraged from merging PRs on their own, except in case of emergency (e.g., critical bug fix and no maintainer is available).
:::
5. Release Policy
Once a feature release candidate is published, no feature PRs are to
be merged into main. Only bugfixes are allowed until the final
release.
</Steps>
## Best Practices
### Clean Commit History
To encourage a clean development log, PR authors are encouraged to use `git rebase -i` to suppress trivial commit messages (e.g., `ruff` and `prettier` formatting fixes) after the PR is accepted but before it is merged.
### Merge Strategy
The maintainer will perform either a **3-way merge** or **squash merge** when merging a PR into the `main` branch. This approach helps avoid rebase conflict hell and maintains a cleaner project history.
### Attribution
The PR author should reference any papers, source code or
documentation that they used while creating the code both in the PR
and as comments in the code itself. If there are any licensing
restrictions, these should be linked to and/or reproduced in the repo
root.
## Summary
This policy ensures that:
- All PRs receive proper review from assigned collaborators
- Maintainers have final oversight before code enters the main branch
- The commit history remains clean and meaningful
- Merge conflicts are minimized through appropriate merge strategies
@@ -0,0 +1,157 @@
---
title: Release Process
lastUpdated: 2025-12-26
---
The Invoke application is published as a python package on [PyPI]. This includes both a source distribution and built distribution (a wheel).
Most users install it with the [Launcher](https://github.com/invoke-ai/launcher/), others with `pip`.
The launcher uses GitHub as the source of truth for available releases.
## Broad Strokes
- Merge all changes and bump the version in the codebase.
- Tag the release commit.
- Wait for the release workflow to complete.
- Approve the PyPI publish jobs.
- Write GH release notes.
## General Prep
Make a developer call-out for PRs to merge. Merge and test things
out. Create a branch with a name like user/chore/vX.X.X-prep and bump the version by editing
`invokeai/version/invokeai_version.py` and commit locally.
## Release Workflow
The `release.yml` workflow runs a number of jobs to handle code checks, tests, build and publish on PyPI.
It is triggered on **tag push**, when the tag matches `v*`.
### Triggering the Workflow
Ensure all commits that should be in the release are merged into this branch, and that you have pulled them locally.
Run `make tag-release` to tag the current commit and kick off the workflow. You will be prompted to provide a message - use the version specifier.
If this version's tag already exists for some reason (maybe you had to make a last minute change), the script will overwrite it.
Push the commit to trigger the workflow.
> In case you cannot use the Make target, the release may also be dispatched [manually] via GH.
### Workflow Jobs and Process
The workflow consists of a number of concurrently-run checks and tests, then two final publish jobs.
The publish jobs require manual approval and are only run if the other jobs succeed.
#### `check-version` Job
This job ensures that the `invokeai` python package version specifier matches the tag for the release. The version specifier is pulled from the `__version__` variable in `invokeai/version/invokeai_version.py`.
This job uses [samuelcolvin/check-python-version].
> Any valid [version specifier] works, so long as the tag matches the version. The release workflow works exactly the same for `RC`, `post`, `dev`, etc.
#### Check and Test Jobs
Next, these jobs run and must pass. They are the same jobs that are run for every PR.
- **`python-tests`**: runs `pytest` on matrix of platforms
- **`python-checks`**: runs `ruff` (format and lint)
- **`frontend-tests`**: runs `vitest`
- **`frontend-checks`**: runs `prettier` (format), `eslint` (lint), `dpdm` (circular refs), `tsc` (static type check) and `knip` (unused imports)
- **`typegen-checks`**: ensures the frontend and backend types are synced
#### `build-wheel` Job
This sets up both python and frontend dependencies and builds the python package. Internally, this runs `./scripts/build_wheel.sh` and uploads `dist.zip`, which contains the wheel and unarchived build.
You don't need to download or test these artifacts.
#### Sanity Check & Smoke Test
At this point, the release workflow pauses as the remaining publish jobs require approval.
It's possible to test the python package before it gets published to PyPI. We've never had problems with it, so it's not necessary to do this.
But, if you want to be extra-super careful, here's how to test it:
- Download the `dist.zip` build artifact from the `build-wheel` job
- Unzip it and find the wheel file
- Create a fresh Invoke install by following the [manual install guide](/start-here/manual/) - but instead of installing from PyPI, install from the wheel
- Test the app
##### Something isn't right
If testing reveals any issues, no worries. Cancel the workflow, which will cancel the pending publish jobs (you didn't approve them prematurely, right?) and start over.
#### PyPI Publish Jobs
The publish jobs will not run if any of the previous jobs fail.
They use [GitHub environments], which are configured as [trusted publishers] on PyPI.
Both jobs require a @lstein or @blessedcoolant to approve them from the workflow's **Summary** tab.
- Click the **Review deployments** button
- Select the environment (either `testpypi` or `pypi` - typically you select both)
- Click **Approve and deploy**
> **If the version already exists on PyPI, the publish jobs will fail.** PyPI only allows a given version to be published once - you cannot change it. If version published on PyPI has a problem, you'll need to "fail forward" by bumping the app version and publishing a followup release.
##### Failing PyPI Publish
Check the [python infrastructure status page] for incidents.
If there are no incidents, contact @lstein or @blessedcoolant, who have owner access to GH and PyPI, to see if access has expired or something like that.
#### `publish-testpypi` Job
Publishes the distribution on the [Test PyPI] index, using the `testpypi` GitHub environment.
This job is not required for the production PyPI publish, but included just in case you want to test the PyPI release for some reason:
- Approve this publish job without approving the prod publish
- Let it finish
- Create a fresh Invoke install by following the [manual install guide](/start-here/manual/), making sure to use the Test PyPI index URL: `https://test.pypi.org/simple/`
- Test the app
#### `publish-pypi` Job
Publishes the distribution on the production PyPI index, using the `pypi` GitHub environment.
It's a good idea to wait to approve and run this job until you have the release notes ready!
## Prep and publish the GitHub Release
1. [Draft a new release] on GitHub, choosing the tag that triggered the release.
2. The **Generate release notes** button automatically inserts the changelog and new contributors. Make sure to select the correct tags for this release and the last stable release. GH often selects the wrong tags - do this manually.
3. Write the release notes, describing important changes. Contributions from community members should be shouted out. Use the GH-generated changelog to see all contributors. If there are Weblate translation updates, open that PR and shout out every person who contributed a translation.
4. Check **Set as a pre-release** if it's a pre-release.
5. Approve and wait for the `publish-pypi` job to finish if you haven't already.
6. Publish the GH release.
7. Post the release in Discord in the [releases](https://discord.com/channels/1020123559063990373/1149260708098359327) channel with abbreviated notes. For example:
> Invoke v5.7.0 (stable): [https://github.com/invoke-ai/InvokeAI/releases/tag/v5.7.0](https://github.com/invoke-ai/InvokeAI/releases/tag/v5.7.0)
>
> It's a pretty big one - Form Builder, Metadata Nodes (thanks @SkunkWorxDark!), and much more.
8. Right click the message in releases and copy the link to it. Then, post that link in the [new-release-discussion](https://discord.com/channels/1020123559063990373/1149506274971631688) channel. For example:
> Invoke v5.7.0 (stable): [https://discord.com/channels/1020123559063990373/1149260708098359327/1344521744916021248](https://discord.com/channels/1020123559063990373/1149260708098359327/1344521744916021248)
## Manual Release
The `release` workflow can be dispatched manually. You must dispatch the workflow from the right tag, else it will fail the version check.
This functionality is available as a fallback in case something goes wonky. Typically, releases should be triggered via tag push as described above.
[PyPI]: https://pypi.org/
[Draft a new release]: https://github.com/invoke-ai/InvokeAI/releases/new
[Test PyPI]: https://test.pypi.org/
[version specifier]: https://packaging.python.org/en/latest/specifications/version-specifiers/
[GitHub environments]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment
[trusted publishers]: https://docs.pypi.org/trusted-publishers/
[samuelcolvin/check-python-version]: https://github.com/samuelcolvin/check-python-version
[manually]: #manual-release
[python infrastructure status page]: https://status.python.org/
@@ -0,0 +1,275 @@
---
title: Development Environment
lastUpdated: 2026-07-01
---
import { LinkCard, Steps, Tabs, TabItem, FileTree, LinkButton } from '@astrojs/starlight/components'
import SystemRequirementsLink from '@components/SystemRequirmentsLink.astro'
:::caution
Invoke uses a SQLite database. When you run the application as a dev install, you accept responsibility for your database. This means making regular backups (especially before pulling) and/or fixing it yourself in the event that a PR introduces a schema change.
If you don't need to persist your db, you can use an ephemeral in-memory database by setting `use_memory_db: true` in your `invokeai.yaml` file. You'll also want to set `scan_models_on_startup: true` so that your models are registered on startup.
:::
## Initial Setup
<Steps>
1. Refer to the system requirements.
<SystemRequirementsLink />
2. Fork and clone the InvokeAI git repository.
<LinkButton
icon="github"
iconPlacement="start"
href="https://github.com/invoke-ai/InvokeAI/fork"
target="_blank"
rel="noopener noreferrer"
>
Fork Repository
</LinkButton>
Next, clone your fork to your local machine. You can use either HTTPS or SSH, depending on your git configuration.
3. This repository uses Git LFS to manage large files. To ensure all assets are downloaded:
- Install git-lfs -> [Download here](https://git-lfs.com/)
- Enable automatic LFS fetching for this repository:
```shell
git config lfs.fetchinclude "*"
```
- Fetch files from LFS (only needs to be done once; subsequent `git pull` will fetch changes automatically):
```shell
git lfs pull
```
4. Create a directory for user data (images, models, db, etc). This is typically at `~/invokeai`, but if you already have a non-dev install, you may want to create a separate directory for the dev install.
5. Follow the [manual install](/start-here/manual/) guide, with some modifications to the install command:
- Use `.` instead of `invokeai` to install from the current directory. You don't need to specify the version.
- Use `uv sync` instead of `uv pip install` so the environment is synchronized from the repository lockfile.
- The current project is installed as an editable install by default. That means your changes to the python code will be reflected when you restart the Invoke server.
- Add the `dev`, `test`, `docs`, and appropriate GPU package options with `--extra`. You may or may not need the `xformers` option - follow the manual install guide to figure that out.
With the modifications made, the sync command should look something like this:
```sh
uv sync --frozen \
--python 3.12 \
--managed-python \
--extra dev \
--extra test \
--extra docs \
--extra cuda \
--extra xformers
```
6. At this point, you should have Invoke installed, a venv set up and activated, and the server running. But you will see a warning in the terminal that no UI was found. If you go to the URL for the server, you won't get a UI.
This is because the UI build is not distributed with the source code. You need to build it manually. End the running server instance.
*(If you only want to edit the docs, you can stop here and skip to the **Documentation** section below.)*
7. Install the frontend dev toolchain, paying attention to versions:
- [`nodejs`](https://nodejs.org/) (tested on LTS, v22)
- [`pnpm`](https://pnpm.io/installation) (tested on v10)
8. Do a production build of the frontend:
```sh
cd <PATH_TO_INVOKEAI_REPO>/invokeai/frontend/web
pnpm i
pnpm build
```
9. Restart the server and navigate to the URL. You should get a UI. After making changes to the python code, restart the server to see those changes.
</Steps>
## Backend Development
Experimenting with changes to the Python source code is a drag if you have to re-start the server and re-load multi-gigabyte models after every change.
For a faster development workflow, add the `--dev_reload` flag when starting the server. The server will watch for changes to all the Python files in the `invokeai` directory and apply those changes to the running server on the fly.
This will allow you to avoid restarting the server (and reloading models) in most cases, but there are some caveats; see the [jurigged documentation](https://github.com/breuleux/jurigged#caveats) for details.
### Testing
The backend tests require the `test` dependency group, which you installed during the initial setup.
See the [Tests](../tests) documentation for information about running and writing tests.
## Frontend Development
You'll need to run `pnpm build` every time you pull in new changes to the frontend.
Another option is to skip the build and instead run the UI in dev mode:
```sh
cd invokeai/frontend/web
pnpm dev
```
This starts a vite dev server for the UI at `127.0.0.1:5173`, which you will use instead of `127.0.0.1:9090`.
The dev mode is substantially slower than the production build but may be more convenient if you just need to test things out. It will hot-reload the UI as you make changes to the frontend code. Sometimes the hot-reload doesn't work, and you need to manually refresh the browser tab.
## Documentation
This documentation is built on [Astro Starlight](https://starlight.astro.build/). It provides a pleasant developer environment for writing engaging documentation, and is built on top of the Astro static site generator, which provides a powerful and flexible framework for building fast, modern websites.
To contribute to the documentation, simply edit the markdown files in the `./docs` directory. You can run a local dev server with hot-reloading for changes made to the docs.
<FileTree>
- **docs**
- public/
- src
- content docs content lives here
- docs
- lib
- components/
- utils/
- content.config.ts
- scripts/
- tests/
- invokeai/
- docker/
- coverage/
</FileTree>
<Steps>
1. Navigate to the `docs` directory and install the dependencies:
```sh
cd docs
pnpm install
```
2. Start the dev server:
```sh
pnpm run dev
```
</Steps>
## VSCode Setup
VSCode offers excellent tools for InvokeAI development, including a python debugger, automatic virtual environment activation, and remote development capabilities.
### Prerequisites
First, ensure you have the following extensions installed:
- [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
- [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance)
It's also highly recommended to install the Jupyter extensions if you plan on working with notebooks:
- [Jupyter](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter)
- [Jupyter Cell Tags](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.vscode-jupyter-cell-tags)
- [Jupyter Notebook Renderers](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter-renderers)
- [Jupyter Slide Show](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.vscode-jupyter-slideshow)
### Configuration
<Tabs>
<TabItem label="Workspace & Interpreter">
Creating a VSCode workspace for working on InvokeAI is highly recommended to hold InvokeAI-specific settings and configs.
1. Open the InvokeAI repository directory in VSCode
2. Go to `File` > `Save Workspace As` and save it *outside* the repository
**Default Python Interpreter**
To enable automatic virtual environment activation:
1. Open the command palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and run `Preferences: Open Workspace Settings (JSON)`
2. Add `python.defaultInterpreterPath` to your settings, pointing to your virtual environment's python executable:
```jsonc
{
"folders": [
{ "path": "InvokeAI" },
{ "path": "/path/to/invokeai_root" }
],
"settings": {
"python.defaultInterpreterPath": "/path/to/invokeai_root/.venv/bin/python"
}
}
```
Now, opening the integrated terminal or running python will automatically use your InvokeAI virtual environment.
</TabItem>
<TabItem label="Type-Checking (Pylance)">
We use Python's typing system in InvokeAI. PR reviews will include checking that types are present and correct.
Pylance provides type checking in the editor. To enable it:
1. Open a Python file
2. Look along the status bar in VSCode for `{ } Python`
3. Click the `{ }`
4. Turn type checking on (Basic is fine)
You'll now see red squiggly lines where type issues are detected. Hover your cursor over the indicated symbols to see what's wrong.
</TabItem>
<TabItem label="Debugging configs">
Debugging configs are managed in a `launch.json` file. Follow the [official guide](https://code.visualstudio.com/docs/python/debugging) to set up your `launch.json` and try it out.
Add these InvokeAI debugging configurations to your `launch.json`:
```jsonc
{
"version": "0.2.0",
"configurations": [
{
"name": "InvokeAI Web",
"type": "python",
"request": "launch",
"program": "scripts/invokeai-web.py",
"args": [
"--root", "/path/to/invokeai_root",
"--host", "0.0.0.0"
],
"justMyCode": true
},
{
"name": "InvokeAI CLI",
"type": "python",
"request": "launch",
"program": "scripts/invokeai-cli.py",
"justMyCode": true
},
{
"name": "InvokeAI Test",
"type": "python",
"request": "launch",
"module": "pytest",
"args": ["--capture=no"],
"justMyCode": true
},
{
"name": "InvokeAI Single Test",
"type": "python",
"request": "launch",
"module": "pytest",
"args": ["tests/nodes/test_invoker.py"],
"justMyCode": true
}
]
}
```
</TabItem>
<TabItem label="Remote Dev">
This provides a smooth experience for running the backend on a powerful Linux machine while developing on another device.
Consult the [official guide](https://code.visualstudio.com/docs/remote/remote-overview) to get it set up. We suggest using VSCode's included settings sync so that your remote dev host has all the same app settings and extensions automatically.
:::tip[Port Forwarding]
Automatic port forwarding can be flaky. You can disable it in `Preferences: Open Remote Settings (ssh: hostname)` by unticking `remote.autoForwardPorts`.
To forward ports reliably, use SSH on the remote dev client:
```bash
ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@remote-dev-host
```
Run this outside the VSCode integrated terminal so it persists across VSCode restarts.
:::
</TabItem>
</Tabs>
@@ -0,0 +1,48 @@
---
title: InvokeAI Development
sidebar:
order: 1
lastUpdated: 2026-02-19
---
import { Card, CardGrid, LinkButton } from '@astrojs/starlight/components';
This section of the documentation is for developers interested in contributing to the InvokeAI codebase, or building on top of it. It includes guides for setting up your development environment, understanding the project structure, and making your first contribution.
<CardGrid>
<Card title="Setup" icon="download">
Instructions for setting up your local development environment, including how to run the project locally and how to set up your tooling.
<LinkButton href="./setup/dev-environment/" icon="right-arrow" variant="primary">
Learn more
</LinkButton>
</Card>
<Card title="Front End" icon="laptop">
An introduction to the front end codebase, including the technologies used and how to get started.
<LinkButton href="./front-end/" icon="right-arrow" variant="secondary">
Learn more
</LinkButton>
</Card>
<Card title="Guides" icon="open-book">
A collection of guides for common development tasks, such as adding new model architectures, making tests, and more.
<LinkButton href="./guides/models" icon="right-arrow" variant="secondary">
Learn more
</LinkButton>
</Card>
<Card title="Architecture" icon="puzzle">
An overview of the InvokeAI architecture, including the major components and how they interact.
<LinkButton href="./architecture/overview/" icon="right-arrow" variant="secondary">
Learn more
</LinkButton>
</Card>
<Card title="Process" icon="list-format">
An overview of the development processes we follow, including our pull request merge policy and release process.
<LinkButton href="./process/pr-merge-policy/" icon="right-arrow" variant="secondary">
Learn more
</LinkButton>
</Card>
</CardGrid>
@@ -0,0 +1,60 @@
---
title: Canvas Projects
sidebar:
badge: New
order: 7
---
import { Steps } from '@astrojs/starlight/components';
Canvas Projects let you save the entire state of a canvas — including all layers, masks, reference images, generation parameters, and LoRAs — into a single `.invk` file that you can reopen later or share with someone else.
`.invk` files are ZIP archives. When saved images can be fetched successfully from the server, they embed the actual image bytes for every layer and reference image, so a project is self-contained: opening it on another machine or after wiping the gallery can restore those images.
## Saving a project
<Steps>
1. Open the canvas and arrange your layers, masks, reference images, and parameters the way you want them.
2. Open the archive menu in the canvas toolbar, or open the canvas context menu and choose **Project**.
3. Choose **Save Canvas Project**.
4. Optionally rename the project (the default is **Canvas Project**).
5. Save the `.invk` file to disk.
</Steps>
What gets saved:
- All raster, inpaint, and control layers, with their image data, transforms, opacity, and lock state.
- All masks.
- Reference images.
- Currently configured generation parameters (model, prompts, scheduler, seed, dimensions, etc.).
- LoRAs and their weights.
## Loading a project
<Steps>
1. Open the archive menu in the canvas toolbar, or open the canvas context menu and choose **Project**.
2. Choose **Load Canvas Project**.
3. Pick the `.invk` file.
</Steps>
When a project is loaded, the canvas is replaced with the project's state. LoRAs are reset first, then re-applied from the project, so opening a project never leaves stale LoRAs from your previous session attached.
### Image deduplication
Loading a project does **not** blindly re-upload every embedded image. Invoke compares each embedded image against what is already in your gallery and only uploads the images that are missing. Re-opening the same project a second time, or opening it shortly after saving it, is therefore very fast — most or all images will already be on the server.
This also means a project shared with another user will upload its missing embedded images the first time it is opened on that user's machine, then become nearly free to re-open after that.
To keep the gallery responsive during large imports, image fetches and uploads are limited to a small number of concurrent requests.
## What `.invk` does *not* save
A `.invk` file is a canvas state snapshot. It does **not** contain:
- The models, LoRAs, or embeddings themselves — only references to them. If you share a project, the recipient needs the same models installed (or compatible substitutes).
- Workflow editor state (use **Save Workflow** in the workflow editor for that).
- Gallery boards or images outside the canvas.
## Sharing projects
`.invk` files are safe to share directly. The recipient loads the file from the canvas toolbar archive menu or canvas context menu. They'll need any referenced models / LoRAs installed locally; if a referenced model is missing, the parameter slot will be empty and they can pick a substitute before generating.
@@ -0,0 +1,75 @@
---
title: Gradient Tool
description: Learn how to paint linear and radial gradients on canvas raster layers.
lastUpdated: 2026-05-16
sidebar:
order: 4
---
import { Card, CardGrid } from '@astrojs/starlight/components';
The Gradient tool paints a smooth transition between your current foreground and background colors on the canvas.
You can activate the Gradient tool from the canvas toolbar.
## Where Gradient Draws
Gradient only draws into the **active raster layer**:
- It does not draw into inpaint masks.
- It does not draw into other non-raster layer types.
- The result is always clipped to the current **generation bounding box**.
If a raster layer is not selected, the tool is unavailable.
## Common Behavior
- Click and drag to define the gradient.
- Release the pointer to commit the gradient.
- Press <kbd>Esc</kbd> to discard the in-progress gradient.
- Hold <kbd>Alt</kbd> to temporarily switch to the color picker.
- Hold <kbd>Space</kbd> to temporarily switch to panning.
The Gradient tool uses the current **FG/BG color pair**:
- The **active** color swatch becomes the start color.
- The **inactive** color swatch becomes the end color.
## Gradient Modes
<CardGrid>
<Card title="Linear">
Click and drag to set the gradient direction. The drag defines the transition from the start color to the end
color.
</Card>
<Card title="Radial">
Click to place the center, then drag outward to set the radius. The gradient fades from the start color at the
center to the end color toward the outside.
</Card>
</CardGrid>
## Clip Gradient
The toolbar includes a **Clip Gradient** toggle:
- **Enabled:** Limits the gradient to the dragged region.
- **Disabled:** Lets the gradient extend across the full current bounding box.
In practice:
- A clipped **linear** gradient is limited to the span you dragged.
- A clipped **radial** gradient is limited to the circle you dragged out.
- With clipping disabled, both modes can be used to wash the entire bbox with a full gradient transition.
## Practical Examples
- Use **Linear** for sky fades, shadow ramps, and broad directional lighting.
- Use **Radial** for vignettes, glows, spotlights, and soft falloff around a focal point.
- Disable **Clip Gradient** when you want a full-bbox color transition.
- Keep **Clip Gradient** enabled when you only want to affect a localized area.
## Summary
The Gradient tool is a raster-only canvas tool for painting linear and radial color transitions. Use it when you want
soft blends between your FG and BG colors, and use **Clip Gradient** to decide whether the effect stays local or fills
the full bbox.
@@ -0,0 +1,77 @@
---
title: Lasso Tool
description: Learn how to create and refine inpaint masks with the Lasso tool.
lastUpdated: 2026-05-15
sidebar:
order: 2
---
import { Card, CardGrid } from '@astrojs/starlight/components';
The Lasso tool is the canvas's dedicated masking tool. It always draws into **inpaint mask layers** and is designed
for quickly defining irregular regions for inpainting.
You can activate the Lasso tool from the canvas toolbar or with the default hotkey <kbd>L</kbd>.
## Where Lasso Draws
Lasso always targets an **enabled inpaint mask**:
- If an enabled inpaint mask is currently selected, Lasso draws into that mask.
- If no enabled inpaint mask is available, Lasso creates a new inpaint mask automatically and commits the contour
there.
:::note
If a disabled inpaint mask is selected, Lasso does not draw into the disabled mask. It creates a new enabled mask for
the next contour instead.
:::
## Common Behavior
- Lasso always commits a **closed contour**.
- Hold <kbd>Ctrl</kbd> on Windows/Linux or <kbd>Cmd</kbd> on macOS to switch to **subtractive** mode and remove area
from the mask instead of adding to it.
- Press <kbd>Esc</kbd> to cancel the current lasso session.
- Hold <kbd>Space</kbd> during an active session to pan the viewport without discarding the unfinished contour.
## Lasso Modes
<CardGrid>
<Card title="Freehand">
Click and drag to sketch an irregular contour. Releasing the pointer closes and commits the contour automatically.
</Card>
<Card title="Polygon">
Click to place vertices. Click the first point to close and commit the contour. Hold <kbd>Shift</kbd> while
placing the next edge to snap it to horizontal, vertical, and 45 degree angles.
</Card>
</CardGrid>
## Moving and Panning During Drawing
The Lasso tool uses <kbd>Space</kbd> for panning in both modes:
- **Freehand:** While drawing, hold <kbd>Space</kbd> to pan the viewport without discarding the unfinished contour.
Release <kbd>Space</kbd> to continue drawing.
- **Polygon:** During an active polygon session, hold <kbd>Space</kbd> to pan the viewport without discarding the
unfinished contour. Release <kbd>Space</kbd> and continue placing points.
This is especially useful when drawing large mask regions that extend beyond the current viewport.
## Working With Masks
- Use **Freehand** for organic shapes like hair, smoke, foliage, fabric, and quick blocking.
- Use **Polygon** when you need straight edges and deliberate corner placement.
- Use **subtractive mode** to trim or punch holes in an existing inpaint mask.
- Use Lasso when you want mask-first editing behavior without first creating a mask layer by hand.
## Practical Notes
- Polygon mode shows the starting point so you can close the contour precisely.
- After at least three polygon points, moving near the start point lets you click it to finish the shape.
- Freehand is faster for loose silhouettes. Polygon is better when edge placement matters.
## Summary
The Lasso tool is the fastest way to create and refine inpaint masks on the canvas. Use Freehand for organic regions,
Polygon for hard edges, and hold <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> whenever you need to subtract from the mask instead of
adding to it.
@@ -0,0 +1,35 @@
---
title: Layer Tips
sidebar:
order: 6
---
A couple of layer-related behaviors that aren't obvious from the canvas UI alone.
## Drag & drop targets
Dragging an image onto the canvas reveals **five** drop zones, arranged as two zones on top and three on the bottom:
| Top row | |
| :--- | :--- |
| **New Raster Layer** | Create a regular raster layer from the dropped image. |
| **New Control Layer** | Create a control layer from the dropped image. |
| Bottom row | |
| :--- | :--- |
| **New Regional Reference** | Use the image as a regional reference. |
| **New Inpaint Mask** | Create a new inpaint mask layer using the image as the mask source. |
| **New Resized Control Layer** | Create a control layer resized to the current canvas dimensions. |
You can drop from the gallery, from disk, or from any panel that shows a draggable image.
## Lock transparency on raster layers
Each raster layer has a **Lock Transparency** toggle (drop icon) in its layer header. When enabled, brush strokes only affect existing non-transparent pixels — painting over transparent areas does nothing. This behaves like Photoshop's "Lock Transparent Pixels".
Typical uses:
- **Recolor an existing shape** without bleeding paint into the empty space around it.
- **Refine details on a subject** that was painted on an otherwise transparent layer, with no risk of growing its silhouette.
Toggle it off to resume normal painting. The lock is per-layer, so different layers can be locked or unlocked independently. Pressure-sensitive pen input and undo/redo both respect the lock.
@@ -0,0 +1,70 @@
---
title: Run Workflow on Canvas
sidebar:
order: 5
---
import { Steps } from '@astrojs/starlight/components';
You can run any workflow against a raster layer directly from the canvas. The selected layer is passed in as the workflow's image input, and the results land in the canvas staging area where you can review and accept them — without leaving the canvas tab.
## Requirements for a workflow
For a workflow to be available from the canvas, it must satisfy three conditions:
1. **Form Builder is enabled.** The workflow's parameters are presented through the Form Builder UI when the workflow is launched from the canvas, so the workflow needs to have a form configured.
2. **At least one image input field.** The layer you right-click on is passed into the first eligible image field as the workflow's input.
3. **At least one `Canvas Output` node.** This is the node that marks which images should be routed back to the canvas staging area.
Workflows that do not meet all three are filtered out of the canvas workflow selector.
## The `Canvas Output` node
`Canvas Output` is a dedicated workflow node that explicitly marks the images you want shown in the canvas staging area. Add it at the end of any branch whose output should appear on the canvas.
A workflow can include **multiple `Canvas Output` nodes**. Each one becomes its own entry in the staging area, with an individually selectable thumbnail. You can navigate between entries with the arrow keys and accept just one of them onto the canvas.
:::note[Why an explicit node?]
Earlier versions detected output images heuristically (by scanning for `board` fields). That was fragile and caused unrelated nodes — for example, `save_image` — to be mistaken for canvas outputs. `Canvas Output` makes the routing intentional.
:::
## Running a workflow
<Steps>
1. On the canvas, **right-click a raster layer** to open its context menu.
2. Choose **Run Workflow**.
3. Pick a workflow from the list. Only workflows that meet the [requirements](#requirements-for-a-workflow) appear here.
4. Adjust any exposed parameters in the form. All form field types are supported: text, numbers, booleans, enums, schedulers, boards, models, and images.
5. Click **Run**. The workflow is queued and the results stream into the staging area as they complete.
</Steps>
The current layer is automatically passed into the workflow's image input — you do not need to select an image manually.
## Reviewing and accepting results
Results appear in the canvas staging area strip at the bottom of the canvas:
- If the workflow has a single `Canvas Output`, you get one thumbnail per run.
- If it has multiple `Canvas Output` nodes, each run produces multiple thumbnails, one per output node.
- Use the staging area's next / previous controls (or arrow keys) to cycle through entries. Navigation wraps across run boundaries.
- Click **Accept** to commit the currently selected entry onto the canvas. Only that single image is committed — siblings stay in staging until you accept or discard them.
## Troubleshooting
### My workflow doesn't appear in the selector
Check, in order:
- The workflow has Form Builder enabled.
- The workflow has at least one image input field.
- The workflow contains at least one `Canvas Output` node.
If any of these is missing, the workflow is hidden.
### Queueing fails with a "BoardField" validation error
This was a known issue with workflows that combined `save_image` and `canvas_output` nodes. It is fixed — update Invoke and try again.
### Errors during execution
Workflow errors are surfaced as toasts and the staging area is cleaned up so it returns to a usable state. Open the queue panel for the full error message.
@@ -0,0 +1,99 @@
---
title: Shapes Tool
description: Learn how to draw filled shapes on raster and inpaint mask layers with the Shapes tool.
lastUpdated: 2026-05-11
sidebar:
order: 1
---
import { Card, CardGrid } from '@astrojs/starlight/components';
The Shapes tool is a general-purpose filled-shape drawing tool for the canvas. It replaces the old Rectangle tool and
adds four shape modes under a single toolbar button:
- **Rect**
- **Oval**
- **Polygon**
- **Freehand**
You can activate the Shapes tool from the canvas toolbar or with the default hotkey <kbd>U</kbd>.
## Where Shapes Draws
Shapes always draws into the **active raster target**:
- On a regular raster layer, Shapes adds filled pixels to that layer.
- On an active inpaint mask layer, Shapes draws directly into the mask.
:::note
Shapes overlaps with some Lasso workflows on mask layers, but the tools are not identical. Lasso is still the more
specialized masking tool and can create a new mask layer automatically when one does not already exist. See the
[Lasso tool guide](./lasso-tool/) for mask-specific behavior.
:::
## Common Behavior
- Shapes preview live while you draw.
- The fill color uses the current active color.
- On a raster layer, the active color's alpha is respected when adding pixels.
- Hold <kbd>Ctrl</kbd> on Windows/Linux or <kbd>Cmd</kbd> on macOS to switch to **subtractive** mode and cut pixels
out of the active layer.
- In subtractive mode, alpha is ignored and the shape fully clears pixels.
- Press <kbd>Esc</kbd> to cancel the current shape session.
:::tip
When subtractive mode is active, the canvas cursor shows a small minus badge so you can tell at a glance that the next
shape will erase instead of fill.
:::
## Shape Modes
<CardGrid>
<Card title="Rect">
Drag to draw a rectangle. Hold <kbd>Shift</kbd> to constrain to a square. Hold <kbd>Alt</kbd> to draw from the
center instead of from a corner.
</Card>
<Card title="Oval">
Drag to draw an ellipse. Hold <kbd>Shift</kbd> to constrain to a perfect circle. Hold <kbd>Alt</kbd> to draw from
the center.
</Card>
<Card title="Polygon">
Click to place vertices. Click the first point to close and commit the shape. Hold <kbd>Shift</kbd> to snap the
pending edge to horizontal, vertical, and 45 degree angles.
</Card>
<Card title="Freehand">
Click and drag to sketch a filled freehand contour. Release the pointer to commit the shape.
</Card>
</CardGrid>
## Moving and Panning During Drawing
The Shapes tool supports different <kbd>Space</kbd> behavior depending on the current mode:
- **Rect / Oval:** While the pointer is still down, hold <kbd>Space</kbd> to move the uncommitted shape instead of
resizing it. Release <kbd>Space</kbd> to continue resizing.
- **Polygon / Freehand:** Hold <kbd>Space</kbd> during an active session to pan the viewport without discarding the
unfinished shape.
This is especially useful when drawing large shapes that extend beyond the current viewport.
## Color Picking While Using Shapes
The <kbd>Alt</kbd> key behaves differently depending on the active Shapes mode:
- **Rect / Oval:** Before you start dragging, <kbd>Alt</kbd> can be used for the temporary color-picker quick-switch.
Once a drag is active, <kbd>Alt</kbd> is reserved for drawing from the center.
- **Polygon:** <kbd>Alt</kbd> remains available for the temporary color-picker quick-switch between vertex placements.
- **Freehand:** <kbd>Alt</kbd> is available before the stroke starts, but not during an active stroke.
## Practical Examples
- Use **Rect** or **Oval** to quickly add clean filled regions.
- Use **Polygon** when you need straight edges and deliberate corner placement.
- Use **Freehand** for irregular organic regions.
- Use **subtractive mode** to cut holes back out of an existing filled region.
## Summary
The Shapes tool is the fastest way to add filled geometric or freeform regions to canvas layers. Use it for structured
fills, mask authoring, and precise subtractive edits without switching away from the current raster target.
@@ -0,0 +1,33 @@
---
title: Text Tool
sidebar:
order: 3
---
import { LinkCard } from '@astrojs/starlight/components';
## Font selection
The Text tool uses a set of predefined font stacks. When you choose a font, the app resolves the first available font on your system from that stack and uses it for both the editor overlay and the rasterized result. This provides consistent styling across platforms while still falling back to safe system fonts if a preferred font is missing.
## Size and spacing
- **Size** controls the font size in pixels.
- **Spacing** controls the line height multiplier (Dense, Normal, Spacious). This affects the distance between lines while editing the text.
## Uncommitted state
While text is uncommitted, it remains editable on-canvas. Access to other tools is blocked. Switching to other tabs (Generate, Upascaling, Workflows etc.) discards the text. The uncommitted box can be moved and rotated:
- **Move:** Hold Ctrl (Windows/Linux) or Command (macOS) and drag to move the text box.
- **Rotate:** Drag the rotation handle above the box. Hold **Shift** while rotating to snap to 15 degree increments.
The text is committed to a raster layer when you press **Enter**. Press **Esc** to discard the current text session.
## For Developers
<LinkCard
title="Canvas Text Tool"
description="Learn about the implementation of the Text tool, including the editor overlay, rasterization"
href="../../development/front-end/text-tool/"
/>
@@ -0,0 +1,54 @@
---
title: Alibaba Cloud DashScope
---
import { Steps } from '@astrojs/starlight/components'
Invoke supports Alibaba Cloud's **DashScope** image generation service, giving access to the **Qwen Image** family and **Wan 2.6** text-to-image. Qwen Image is particularly strong at bilingual (Chinese / English) text rendering.
## Getting an API Key
<Steps>
1. Sign in to [Alibaba Cloud Model Studio](https://www.alibabacloud.com/en/product/modelstudio) (the international DashScope portal).
2. Enable **DashScope** and activate the image generation models you plan to use.
3. Create an API key from the **API Keys** section of the console.
</Steps>
## Configuration
Add your key to `api_keys.yaml` in your Invoke root directory:
```yaml
external_alibabacloud_api_key: "your-dashscope-api-key"
# Optional — default is the international endpoint. Use the China endpoint if your account lives there:
# https://dashscope.aliyuncs.com
external_alibabacloud_base_url: "https://dashscope-intl.aliyuncs.com"
```
Restart Invoke for the change to take effect.
:::note[International vs. China endpoints]
DashScope has separate international (`dashscope-intl.aliyuncs.com`) and China (`dashscope.aliyuncs.com`) deployments. Your API key only works on the deployment it was issued on — if you get authentication errors, check that `external_alibabacloud_base_url` matches.
:::
## Available Models
| Model | Modes | Aspect Ratios | Batch | Notes |
| --- | --- | --- | --- | --- |
| **Qwen Image 2.0 Pro** | txt2img | 1:1, 4:3, 3:4, 16:9, 9:16 | up to 4 | Best quality, 2K output, excellent bilingual text. |
| **Qwen Image 2.0** | txt2img | 1:1, 4:3, 3:4, 16:9, 9:16 | up to 4 | Faster / cheaper 2K sibling of 2.0 Pro. |
| **Qwen Image Max** | txt2img | 1:1, 4:3, 3:4, 16:9, 9:16 | up to 4 | High quality at ~1.3K native size. |
| **Qwen Image Edit Max** | txt2img (with reference images) | 1:1, 4:3, 3:4, 16:9, 9:16 | up to 4 | Reference-image-driven generation with industrial / geometric reasoning. Accepts up to 14 reference images. |
| **Wan 2.6 Text-to-Image** | txt2img | 1:1, 4:3, 3:4, 16:9, 9:16 | up to 4 | Photorealistic T2I at 1K. |
All models support **seed**. Negative prompts are not currently plumbed through to DashScope, so the negative prompt input is ignored for these providers. None of the Alibaba Cloud models support img2img (denoising-strength edits) or inpaint (mask-based edits) in Invoke today.
## Tips
<Steps>
1. Bilingual prompts. Qwen Image is unusually good at rendering Chinese text and mixed-language prompts — it's a strong choice when your prompt or desired output contains non-Latin script.
2. Reference-image input is only accepted by Qwen Image Edit Max — provide images via the reference-images panel. Masks and denoising strength are not supported for any Alibaba Cloud model.
3. Batching is capped at 4 images per request. Larger batches are split across multiple API calls.
4. Costs vary per model — Qwen Image 2.0 Pro is the most expensive, Qwen Image 2.0 the cheapest of the 2.0 family. Check Alibaba Cloud's pricing page before running large batches.
</Steps>
@@ -0,0 +1,48 @@
---
title: Google Gemini
---
import { Steps } from '@astrojs/starlight/components'
Invoke supports Google's Gemini image generation models through the Gemini API. This provider is a good fit if you want high-quality text-to-image and reference-based image edits without running a local model.
## Getting an API Key
<Steps>
1. Open [Google AI Studio](https://aistudio.google.com/) and sign in with your Google account.
2. Generate a new API key.
3. Note the key — it will only be shown once.
</Steps>
## Configuration
Add your key to `api_keys.yaml` in your Invoke root directory:
```yaml
external_gemini_api_key: "your-gemini-api-key"
# Optional — only set this if you need to route requests through a different endpoint
external_gemini_base_url: "https://generativelanguage.googleapis.com"
```
Restart Invoke for the change to take effect.
## Available Models
| Model | Modes | Reference Images | Notes |
| --- | --- | --- | --- |
| **Gemini 2.5 Flash Image** | txt2img | Yes | 10 aspect ratios, fixed per-ratio resolutions. |
| **Gemini 3 Pro Image Preview** | txt2img | Up to 14 (6 object + 5 character) | 1K / 2K / 4K resolution presets. |
| **Gemini 3.1 Flash Image Preview** | txt2img | Up to 14 (10 object + 4 character) | 512 / 1K / 2K / 4K resolution presets. |
Reference-image input is used to condition generation but counts as txt2img — neither img2img (denoising strength) nor inpaint (mask) is supported for Gemini.
All Gemini models are single-image-per-request — batch size is fixed at 1. To generate multiple variations, queue multiple invocations.
## Tips
<Steps>
1. Reference images are sent directly to the API as inlined PNG data. Large references increase request latency and cost — crop tightly where possible.
2. Aspect ratios are mapped to the closest Gemini-supported ratio. For Gemini 3 models, use the resolution presets to stay at the provider's native output sizes and avoid unnecessary rescaling.
3. Pricing varies by model and region. Check Google's documentation before running large batches.
</Steps>
@@ -0,0 +1,58 @@
---
title: External Models
---
External models let you generate images in Invoke by calling third-party image generation APIs instead of running a model locally. This is useful when:
- You don't have the GPU or VRAM to run a model locally.
- You want access to closed-source models (e.g. GPT Image, Gemini).
- You need a specific provider capability (very high resolutions, fast batches, bilingual text rendering, etc.).
External models appear in the model picker alongside locally installed models. Generations are routed to the provider's API, billed against your provider account, and the resulting images are imported back into Invoke like any other generation.
## Supported Providers
- [Google Gemini](/features/external-models/gemini/) — Gemini 2.5 Flash Image, Gemini 3 Pro Image Preview, Gemini 3.1 Flash Image Preview
- [OpenAI](/features/external-models/openai/) — GPT Image 1 / 1.5 / 1-mini, DALL·E 3
- [BytePlus Seedream](/features/external-models/seedream/) — Seedream 5.0, 5.0 Lite, 4.5, 4.0
- [Alibaba Cloud DashScope](/features/external-models/alibabacloud/) — Qwen Image 2.0 / 2.0 Pro / Max / Edit Max, Wan 2.6 T2I
## Configuring API Keys
External provider credentials are stored in a dedicated `api_keys.yaml` file alongside `invokeai.yaml` in your Invoke root directory.
```yaml
# api_keys.yaml
external_gemini_api_key: "your-gemini-api-key"
external_openai_api_key: "your-openai-api-key"
# Optional: override the provider base URL (e.g. for a compatible proxy or regional endpoint)
external_gemini_base_url: "https://generativelanguage.googleapis.com"
external_openai_base_url: "https://api.openai.com"
```
Restart Invoke after editing `api_keys.yaml` so the new values are picked up.
!!! warning "Keep your keys private"
`api_keys.yaml` contains secrets. Do not commit it to version control and do not share it with other users of your machine.
## Installing External Models
External models are listed in the starter models dialog under their provider. Install them like any other starter model — Invoke records a model reference but does not download weights (there are no weights to download).
Once installed, external models show up everywhere a model can be selected. Choose one, set the usual parameters (prompt, dimensions, num images, etc.), and invoke as normal.
## Capabilities and Settings Visibility
Each external model declares its own **capabilities** — for example:
- Which generation modes it supports (`txt2img`, `img2img`). Inpainting is not currently supported by any external provider.
- Whether it accepts reference images, and how many.
- Which aspect ratios and resolutions it allows.
- Whether it supports a negative prompt, seed, or batch size > 1.
Invoke uses these capabilities to drive the UI: only the settings a given model actually supports will be shown in the parameters panel. If a field you expect is missing, it's because the selected model does not support it.
## Costs and Rate Limits
External providers charge for each request. Check the provider's pricing page before running large batches. Rate-limit errors from the provider are surfaced in Invoke as generation failures — wait a moment and try again, or lower your concurrent batch size.
@@ -0,0 +1,65 @@
---
title: OpenAI
---
import { Steps } from '@astrojs/starlight/components'
Invoke supports OpenAI's image generation models — the GPT Image family and DALL·E 3 — through the OpenAI API.
:::note[DALL·E 2 removed]
DALL·E 2 was deprecated by OpenAI and is scheduled for shutdown on 2026-05-12. It is no longer offered as a starter model in Invoke.
:::
## Getting an API Key
<Steps>
1. Open the [OpenAI API Platform](https://platform.openai.com/api-keys) and sign in.
2. Create a new secret API key.
3. Make sure your account has billing set up — image endpoints are paid per request.
</Steps>
## Configuration
Add your key to `api_keys.yaml` in your Invoke root directory:
```yaml
external_openai_api_key: "sk-..."
# Optional — use this to point at a compatible proxy or Azure OpenAI deployment
external_openai_base_url: "https://api.openai.com"
```
Restart Invoke for the change to take effect.
## Available Models
| Model | Modes | Aspect Ratios | Batch | Notes |
| --- | --- | --- | --- | --- |
| **GPT Image 1.5** | txt2img, img2img | 1:1, 3:2, 2:3 | up to 10 | Fastest and cheapest GPT Image model. |
| **GPT Image 1** | txt2img, img2img | 1:1, 3:2, 2:3 | up to 10 | Highest quality of the GPT Image family. |
| **GPT Image 1 Mini** | txt2img, img2img | 1:1, 3:2, 2:3 | up to 10 | ~80% cheaper than GPT Image 1. |
| **DALL·E 3** | txt2img only | 1:1, 7:4, 4:7 | 1 | No reference-image / edit support. |
Inpainting (mask-based editing) is not currently supported for any OpenAI model in Invoke. img2img on the GPT Image family routes through the `/v1/images/edits` endpoint without a mask.
## Provider-Specific Options
For **GPT Image** models, Invoke surfaces two provider-specific options in the parameters panel:
- **Quality** — `low`, `medium`, `high`, or `auto`. Higher quality costs more and takes longer.
- **Background** — `auto`, `transparent`, or `opaque`. Use `transparent` for PNG output with an alpha channel.
DALL·E 2 and DALL·E 3 do not expose these options.
## How Requests Are Routed
- Pure text-to-image requests hit `/v1/images/generations`.
- Any request with an init image or reference images is sent to `/v1/images/edits` instead. This is done transparently — you don't need to pick an endpoint.
## Tips
<Steps>
1. Batching on GPT Image tops out at 10 per request. Larger batches are split into multiple API calls.
2. Costs can climb quickly with high-quality GPT Image generations. Start with GPT Image 1 Mini when iterating on prompts.
3. Rate limits from OpenAI surface as failed invocations — retry after a short wait.
</Steps>
@@ -0,0 +1,68 @@
---
title: BytePlus Seedream
---
import { Steps } from '@astrojs/starlight/components'
Invoke supports BytePlus's **Seedream** image generation family through the BytePlus Ark API. Seedream is a strong fit for 2K/4K generations and multi-reference image composition.
## Getting an API Key
<Steps>
1. Open the [BytePlus Console](https://console.byteplus.com/) and sign in.
2. Enable the **Ark** (model serving) product.
3. Create an API key with access to the Seedream models you plan to use.
</Steps>
## Configuration
Add your key to `api_keys.yaml` in your Invoke root directory:
```yaml
external_seedream_api_key: "your-seedream-api-key"
# Optional — change only if you need a different regional endpoint
external_seedream_base_url: "https://ark.ap-southeast.bytepluses.com"
```
Restart Invoke for the change to take effect.
## Available Models
| Model | Modes | Reference Images | Batch | Native Size |
| --- | --- | --- | --- | --- |
| **Seedream 5.0** | txt2img, img2img | up to 14 | up to 15 | 2K |
| **Seedream 5.0 Lite** | txt2img, img2img | up to 14 | up to 15 | 2K |
| **Seedream 4.5** | txt2img, img2img | up to 14 | up to 15 | 2K |
| **Seedream 4.0** | txt2img, img2img | up to 14 | up to 15 | 2K |
The 4.x / 5.x models are batch-capable and accept up to 14 reference images per request.
:::note[Model IDs]
BytePlus uses date-stamped model IDs (e.g. `seedream-5-0-260128`). When BytePlus releases a new dated revision, the starter model IDs in Invoke need to be updated. Seedream 3.0 T2I (`seedream-3-0-t2i-250415`) was deprecated by BytePlus and replaced by Seedream 4.0.
:::
### Supported Aspect Ratios
All Seedream models share the same aspect ratio set: `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `9:16`, `16:9`, `21:9`, rendered at 2K.
## Provider-Specific Options
Seedream exposes two provider-specific toggles in the parameters panel:
- **Watermark** — When enabled, BytePlus adds a small watermark to the output. Off by default.
- **Optimize Prompt** — When enabled, BytePlus rewrites your prompt server-side for better generation quality. Useful for short prompts; disable if you want the exact wording preserved.
**Seed** and **guidance scale** are not accepted by the 4.x / 5.x family.
## Reference Images
4.x and 5.x Seedream models accept up to 14 reference images alongside the prompt. Invoke's standard reference-image panel is used — drag images in, and they are forwarded as base64 PNGs to the API.
## Tips
<Steps>
1. For multi-image composition (e.g. character + product), Seedream 4.5 is a good default.
2. When running large batches (`num_images > 1` on 4.x / 5.x), Invoke uses the `sequential_image_generation` API flag — each image is returned as it completes.
3. Set `external_seedream_base_url` if you need to route through a region-specific Ark endpoint.
</Steps>
@@ -0,0 +1,670 @@
---
title: Multi-User Administrator Guide
description: How to set up and manage a multi-user InvokeAI installation.
sidebar:
order: 4
---
import { Steps } from '@astrojs/starlight/components'
## Overview
This guide is for administrators managing a multi-user InvokeAI
installation. It covers initial setup, user management, security best
practices, and troubleshooting.
## Prerequisites
Before enabling multi-user support, ensure you have:
- InvokeAI installed and running
- Access to the server filesystem (for initial setup)
- Understanding of your deployment environment
- Backup of your existing data (recommended)
## Initial Setup
### Activating Multiuser Mode
To put InvokeAI into multiuser mode, you will need to add the option `multiuser: true` to its configuration file. This file is located at `INVOKEAI_ROOT/invokeai.yaml`. With the InvokeAI backend halted, add the new configuration option to the end of the file with a text editor so that it looks like this:
```yaml
# Internal metadata - do not edit:
schema_version: 4.0.2
# Enable/disable multi-user mode
multiuser: true
```
Then restart the InvokeAI server backend from the command line or using the launcher.
:::note[Reverting to single-user mode]
If at any time you wish to revert to single-user mode, simply comment out the `multiuser` line, or change "true" to "false". Then restart the server. Because of the way that browsers cache pages, users with open InvokeAI sessions may need to force-refresh their browsers.
:::
### Queue Scheduling
The image generation queue is processed by a single worker, so jobs run one at a time. The `session_queue_mode` option controls the order in which pending jobs are selected:
| Mode | Behavior |
|------|----------|
| `round_robin` (default) | Interleaves jobs across users — each user is served one job before any user is served a second. A single user enqueuing a large batch can no longer monopolize the queue. |
| `FIFO` | Strict first-come, first-serve. Jobs run in the order they were enqueued (respecting priority), so a large batch drains completely before the next user's jobs start. |
Set it in `invokeai.yaml`:
```yaml
multiuser: true
session_queue_mode: round_robin # or FIFO
```
Or via the `INVOKEAI_SESSION_QUEUE_MODE` environment variable:
```bash
INVOKEAI_SESSION_QUEUE_MODE=FIFO
```
:::note
`session_queue_mode` only applies in multiuser mode. In single-user mode the queue is always FIFO regardless of this setting, since all jobs belong to the same account.
:::
Round-robin fairness is determined from when each user's jobs were last started. Retained terminal queue history (see [`max_queue_history`](#configuration-reference)) does not slow dequeue scheduling — each dequeue's cost scales with the number of users who currently have pending jobs, not with the size of the history.
### First Administrator Account
When InvokeAI starts for the first time in multi-user mode, you'll see the **Administrator Setup** dialog.
**Setup Steps:**
<Steps>
1. **Email Address**: Enter a valid email address (this becomes your username)
- Example: `admin@example.com` or `admin@localhost` for testing
- Must be a valid email format
- Cannot be changed later without database access
2. **Display Name**: Enter a friendly name
- Example: "System Administrator" or your real name
- Can be changed later in your profile
- Visible to other users in shared contexts
3. **Password**: Create a strong administrator password
- **Minimum requirements:**
- At least 8 characters long
- Contains uppercase letters (A-Z)
- Contains lowercase letters (a-z)
- Contains numbers (0-9)
- **Recommended:**
- Use 12+ characters
- Include special characters (!@#$%^&*)
- Use a password manager to generate and store
- Don't reuse passwords from other services
4. **Confirm Password**: Re-enter the password
5. Click **Create Administrator Account**
</Steps>
:::caution[Important]
Store these credentials securely! The first administrator account can reset the password to something new, but cannot retrieve a lost one.
:::
### Configuration
InvokeAI can run in single-user or multi-user mode, controlled by the `multiuser` configuration option in `invokeai.yaml`:
```yaml
# Enable/disable multi-user mode
multiuser: true # Enable multi-user mode (requires authentication)
# Optional password policy
strict_password_checking: true # Enforce uppercase/lowercase/number requirements
```
JWT secrets are generated automatically and stored in the database. Session lifetimes default to 24 hours, or 7 days when the user selects "Remember me". See Secret Key Management below if you need to rotate the JWT secret.
:::caution[Mode Switching Behavior]
**Switching to Single-User Mode:** If boards or images were created in multi-user mode, they will all be combined into a single unified view when switching to single-user mode.
**Switching to Multi-User Mode:** Legacy boards and images created under single-user mode will be owned by an internal user named "system." Only the Administrator will have access to these legacy assets. A utility to migrate these legacy assets to another user will be part of a future release.
:::
### Migration from Single-User
When upgrading from a single-user installation or switching modes:
<Steps>
1. **Automatic Migration**: The database will automatically migrate to multi-user schema when multi-user mode is first enabled
2. **Legacy Data Ownership**: Existing data (boards, images, workflows) created in single-user mode is assigned to an internal user named "system"
3. **Administrator Access**: Only administrators will have access to legacy "system"-owned assets when in multi-user mode
4. **No Data Loss**: All existing content is preserved
</Steps>
**Migration Process:**
```bash
# Backup your database first
cp databases/invokeai.db databases/invokeai.db.backup
# Enable multi-user mode in invokeai.yaml
# multiuser: true
# Start InvokeAI (migration happens automatically)
invokeai-web
# Complete the administrator setup dialog
# Legacy data will be owned by "system" user
```
:::note[Legacy Asset Migration]
A utility to migrate legacy "system"-owned assets to specific user accounts will be available in a future release. Until then, administrators can access and manage all legacy content.
:::
## User Management
### Creating Users
Administrators can create and modify users (including other
administrators) via a built-in web interface or using command-line
scripts.
#### **Via the Web Frontend:**
Please see the Multi-User Guide's section on [Adding and Modifying Users](./user-guide#adding-and-modifying-users)
for a walk-through.
#### **Via Command Line Scripts:**
##### Command-line User Management Scripts
Administrators can also use a series of command-line scripts to add, modify, or delete users. If you use the launcher, click the ">" icon to enter the command-line interface. Otherwise, if you are a native command-line user, activate the InvokeAI environment from your terminal.
All command-line arguments are optional. The scripts will prompt you to provide any missing arguments.
The commands are:
| Name | Function | Example CLI Usage |
|--------------------|---------------|--------------------|
|**invoke-useradd** | add a user | `invoke-useradd --email user@example.com --name "Example User" --password "badpassword"` |
|**invoke-usermod** | modify a user | `invoke-usermod --email user@example.com --name "Mr. Example User" --password "8adsf2**%"` |
|**invoke-userdel** | delete a user | `invoke-userdel --email user@example.com --force` |
|**invoke-userlist** | list all users| `invoke-userlist` |
Pass the `--help` argument to get the usage of each script. For example:
```bash
> invoke-useradd --help
usage: invoke-useradd [-h] [--root ROOT] [--email EMAIL] [--password PASSWORD] [--name NAME] [--admin]
Add a user to the InvokeAI database
options:
-h, --help show this help message and exit
--root ROOT, -r ROOT Path to the InvokeAI root directory. If omitted, the root is resolved in this order: the $INVOKEAI_ROOT environment
variable, the active virtual environment's parent directory, or $HOME/invokeai.
--email EMAIL, -e EMAIL
User email address
--password PASSWORD, -p PASSWORD
User password
--name NAME, -n NAME User display name (optional)
--admin, -a Make user an administrator
If no arguments are provided, the script will run in interactive mode.
```
:::danger[Data Loss]
Deleting a user removes the user record and cascades to their sessions, board shares, sent
invitations, and per-user client state. It does **not** delete the boards, images, workflows, queue
items, or style presets they created — those rows remain in the database, owned by a user_id that no
longer exists, and will not appear in any user's gallery. Physical image files in `outputs/images`
are also left in place until a gallery maintenance script is run to remove orphan images.
If you want their content gone as well, reassign or delete it before deleting the user. Back up the
database first if recovery might be needed.
:::
### Viewing User Activity
**Queue Management:**
There is no separate admin-only queue view. When signed in as an administrator, the regular queue
panel automatically shows every user's queue items (each item is labelled with the submitting user's
display name or email), and you can cancel or clear any of them. There is no built-in UI to filter
the queue by user; use your browser's find-in-page to scan by name if needed.
## Model Management
As an administrator, you have full access to the [Model
Manager](/concepts/models) and can install, edit and delete
models just as in single-user mode. Unprivileged users, however, can
view the models previously installed, but cannot add or modify them.
## Security
:::note[Strict Password Checking]
It is recommended that you enable strict password checking. This will
force all users to select good passwords that follow the
"minimal requirements" below. Do this by adding `strict_password_checking` to
the `invokeai.yaml` configuration file:
```
strict_password_checking: true
```
:::
### Password Policies
**Minimal Requirements:**
- Minimum 8 characters
- Must contain uppercase letters
- Must contain lowercase letters
- Must contain numbers
If `strict_password_checking` is active (recommended), then these
minimal requirements will be enforced and users will not be able to
proceed until they have picked a password that satisfies
them. Otherwise, the user will simply be warned when they use a weak
password.
**Recommended Policies:**
- Require 12+ character passwords
- Include special characters
- Implement password rotation every 90 days
- Prevent password reuse
### Session Management
**Session Security and Token Management:**
This system uses stateless JWT tokens with HMAC signatures to identify users after they provide their initial credentials. The tokens will persist for 24 hours by default, or for 7 days if the user clicks the "Remember me" checkbox at login. Expired tokens are automatically rejected and the user will have to log in again.
At the client side, tokens are stored in browser localStorage. Logging out clears them. No server-side session storage is required.
The tokens include the user's ID, email, and admin status, along with an HMAC signature.
### Secret Key Management
**Important:** The JWT secret key must be kept confidential.
To generate tokens, each InvokeAI instance has a distinct secret JWT
key that must be kept confidential. The key is stored in the
`app_settings` table of the InvokeAI database within a field value
named `jwt_secret`.
The secret key is automatically generated during database creation or
migration. If you wish to change the key, you may generate a
replacement using either of these commands:
```bash
# Python
python -c "import secrets; print(secrets.token_urlsafe(32))"
# OpenSSL
openssl rand -base64 32
```
Then cut and paste the printed secret into this Sqlite3 command:
```bash
sqlite3 INVOKE_ROOT/databases/invokeai.db 'update app_settings set value="THE_SECRET" where key="jwt_secret"'
```
(replace INVOKE_ROOT with your InvokeAI root directory and THE_SECRET with the new secret).
After this, restart the server. All logged in users will be logged out and will need to provide their usernames and passwords again.
### Hosting a Shared InvokeAI Instance
The multiuser feature allows you to run an InvokeAI backend that can be accessed by your friends and family across your home network. It is also possible to host a backend that is accessible over the Internet.
By default, InvokeAI runs on `localhost`, IP address `127.0.0.1`, which is only accessible to browsers running on the same machine as the backend. To make the backend accessible to any machine on your home or work LAN, add the line `host: 0.0.0.0` to the InvokeAI configuration file, usually stored at `INVOKE_ROOT/invokeai.yaml`.
Here is a minimal example.
```yaml
# Internal metadata - do not edit:
schema_version: 4.0.2
# Put user settings here - see https://invoke-ai.github.io/InvokeAI/configuration/:
multiuser: true
host: 0.0.0.0
```
After relaunching the backend you will be able to reach the server from other machines on the LAN using the server machine's IP address or hostname and port 9090.
#### Making InvokeAI Accessible to the Internet
:::danger[Use at your own risk]
The InvokeAI team has done its best to make the software free of exploitable bugs, but the software has not undergone a rigorous security audit or intrusion testing. Use at your own risk.
:::
It is also possible to create a (semi) public server accessible from the Internet. The details of how to do this depend very much on your home or corporate router/firewall system and are beyond the scope of this document.
If you expose InvokeAI to the Internet, there are a number of precautions to take. Here is a brief list of recommended network security practices.
**HTTPS Configuration:**
For internet deployments, always use HTTPS:
```nginx
# Use a reverse proxy like nginx or Traefik
# Example nginx configuration:
server {
listen 443 ssl http2;
server_name invoke.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:9090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
**Firewall Rules:**
It is best to restrict access to trusted networks and remote IP addresses, or use a VPN to connect to your home network. Rate limit connections to InvokeAI's authentication endpoint `http://your.host:9090/api/v1/auth/login`.
**Backup and Recovery:**
It is always a good idea to periodically backup your InvokeAI database and images, but especially
so if the server is publicly accessible to the Internet.
**Manual Backup:**
```bash
# Stop InvokeAI
# Copy database file
cd INVOKE_ROOT
cp databases/invokeai.db databases/invokeai.db.$(date +%Y%m%d)
# Or create compressed backup
tar -czf invokeai_backup_$(date +%Y%m%d).tar.gz databases/
```
**Automated Backup Script:**
```bash
#!/bin/bash
# backup_invokeai.sh
INVOKE_ROOT="/path/to/invoke_root"
BACKUP_DIR="/path/to/backups"
DB_PATH="$INVOKE_ROOT/databases/invokeai.db"
DATE=$(date +%Y%m%d_%H%M%S)
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Copy database
cp "$DB_PATH" "$BACKUP_DIR/invokeai_$DATE.db"
# Keep only last 30 days
find "$BACKUP_DIR" -name "invokeai_*.db" -mtime +30 -delete
echo "Backup completed: invokeai_$DATE.db"
```
**Schedule with cron:**
```bash
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * /path/to/backup_invokeai.sh
```
**Restore from Backup:**
```bash
# Stop InvokeAI
# Replace current database with backup
cd INVOKE_ROOT
cp databases/invokeai.db databases/invokeai.db.old # Save current
cp databases/invokeai_backup.db databases/invokeai.db
# Restart InvokeAI
invokeai-web
```
**Disaster Recovery — Complete System Backup:**
Include these directories/files:
- `databases/` — All database files
- `models/` — Installed models (if locally stored)
- `outputs/` — Generated images
- `invokeai.yaml` — Configuration file
- Any custom scripts or modifications
**Recovery Process:**
<Steps>
1. Install InvokeAI on new system
2. Restore configuration file
3. Restore database directory
4. Restore models and outputs
5. Verify file permissions
6. Start InvokeAI and test
</Steps>
## Troubleshooting
### User Cannot Login
**Symptom:** User reports unable to log in
**Diagnosis:**
1. Verify account exists and is active
```bash
sqlite3 databases/invokeai.db "SELECT * FROM users WHERE email = 'user@example.com';"
```
2. Check password (have user try resetting)
3. Verify account is active (`is_active = 1`)
4. Check for account lockout (if implemented)
**Solutions:**
- Reset user password
- Reactivate disabled account
- Verify email address is correct
- Check system logs for auth errors
### Database Locked Errors
**Symptom:** "Database is locked" errors
**Causes:**
- Concurrent write operations
- Long-running transactions
- Backup process accessing database
- File system issues
**Solutions:**
```bash
# Check for locks
fuser databases/invokeai.db
# Increase timeout (in config)
# Or switch to WAL mode:
sqlite3 databases/invokeai.db "PRAGMA journal_mode=WAL;"
```
### Forgotten Admin Password
**Recovery Process:**
<Steps>
1. Stop InvokeAI
2. Direct database access:
```bash
sqlite3 databases/invokeai.db
```
3. Reset admin password (requires password hash):
```sql
-- Generate hash first using Python:
-- from passlib.context import CryptContext
-- pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
-- print(pwd_context.hash("NewPassword123"))
UPDATE users
SET password_hash = '$2b$12$...'
WHERE email = 'admin@example.com';
```
4. Restart InvokeAI
</Steps>
:::note[Alternative Step 3]
Remove the admin from the database entirely in order
to trigger the setup process when InvokeAI restarts:
```sql
DELETE FROM users
WHERE email = 'admin@example.com';
```
:::
### Performance Issues
**Symptom:** Slow generation or UI
**Diagnosis:**
<Steps>
1. Check active generation count
2. Review resource usage (CPU/GPU/RAM)
3. Check database size and performance
4. Review network latency
</Steps>
**Solutions:**
- Limit concurrent generations
- Increase hardware resources
- Optimize database (`VACUUM`, `ANALYZE`)
- Add indexes for slow queries
- Consider load balancing
### Migration Failures
**Symptom:** Database migration fails on upgrade
**Prevention:**
- Always backup before upgrading
- Test migration on copy of database
- Review migration logs
**Recovery:**
```bash
# Restore backup
cp databases/invokeai.db.backup databases/invokeai.db
# Try migration again with verbose logging
invokeai-web --log-level DEBUG
```
## Configuration Reference
### Complete Configuration Example for a Public Site
```yaml
# invokeai.yaml - Multi-user configuration
# Internal metadata - do not edit:
schema_version: 4.0.2
# Put user settings here
multiuser: true
# Server
host: "0.0.0.0"
port: 9090
# Performance
enable_partial_loading: true
precision: float16
pytorch_cuda_alloc_conf: "backend:cudaMallocAsync"
hashing_algorithm: blake3_multi
```
## Frequently Asked Questions
### How many users can InvokeAI support?
The backend will support dozens of concurrent users. However, because the image generation queue is single-threaded, only one job runs at a time. By default jobs are scheduled **round-robin** across users, so each user is served one job per turn and no single user can monopolize the queue with a large batch. You can switch to strict first-come, first-serve ordering with `session_queue_mode: FIFO` — see [Queue Scheduling](#queue-scheduling).
A future version of InvokeAI may support concurrent execution on systems with multiple GPUs/graphics cards.
### Can I integrate with existing authentication systems?
OAuth2/OpenID Connect support is planned for a future release. Currently, InvokeAI uses its own authentication system.
### How do I audit user actions?
Full audit logging is planned for a future release. Currently, you can:
- Monitor the generation queue
- Review database changes
- Check application logs
### Can users have different model access?
Currently all users can view and use all installed models. Per-user
model access is a possible enhancement. Please let the development
team know if you want this feature.
### How do I handle user data when they leave?
Best practice:
<Steps>
1. Deactivate the account first
2. Transfer ownership of shared boards
3. After transition period, delete the account
4. Or keep the account deactivated for audit purposes
</Steps>
### What's the licensing impact of multi-user mode?
InvokeAI remains under its existing license. Multi-user mode does not change licensing terms.
## Getting Help
### Support
- **General Documentation**: [InvokeAI Docs](https://invoke.ai/)
- **User Guide**: [For Users](/features/multi-user-mode/user-guide/)
- **API Guide**: [For Developers](/features/multi-user-mode/api-guide/)
- **Discord**: [Join Community](https://discord.gg/ZmtBAhwWhy)
- **GitHub Issues**: [Report Problems](https://github.com/invoke-ai/InvokeAI/issues)
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,366 @@
---
title: Multi-User Guide
description: How to use InvokeAI in multi-user mode as an end user.
sidebar:
order: 3
---
import { Steps } from '@astrojs/starlight/components'
## Overview
Multi-User mode is a recent feature (introduced in version 6.12), which allows multiple individuals to share a single InvokeAI server while keeping their work separate and organized. Each user has their own username and login password, images, assets, image boards, customization settings and workflows.
Two types of users are recognized:
- A user with **Administrator** status can add, remove and modify other users, and can install models. They also have the ability to view the full session queue and pause or kill other users' jobs.
- **Non-administrator** users can modify their own profile but not others. They also do not have the ability to install or configure models, but must ask an Administrator to do this task. When viewing the generation queue, they can see the full details of their own jobs, but jobs owned by other users will have the user id, generation parameters, and other details redacted.
Multiple users can be granted Administrator status.
---
## Getting Started
To activate Multi-User mode, open the `INVOKEAI_ROOT/invokeai.yaml` configuration file in a text editor. Add this line anywhere in the file:
```yaml
multiuser: true
```
You may also wish to make InvokeAI available to other machines on your local LAN. Add an additional line to `invokeai.yaml`:
```yaml
host: 0.0.0.0
```
Restart the server. It will now be in multi-user mode. If you enabled the `host` option, other users on your home or office LAN will be able to reach it by browsing to the IP address of the machine the backend is running on (`http://host-ip-address:9090`).
:::tip[Do not expose InvokeAI to the internet]
It is not recommended to expose the InvokeAI host to the internet due to security concerns.
:::
### Initial Setup (First Time in Multi-User Mode)
If you're the first person to access a fresh InvokeAI installation in multi-user mode, you'll see the **Administrator Setup** dialog:
![Administrator Setup Screen](./assets/admin-setup.png)
Now:
<Steps>
1. Enter your email address (this will be your login name)
2. Create a display name (this will be the name other users see)
3. Choose a strong password. The following criteria are required with `strict_password_checking: true`.
- At least 8 characters long
- Contains uppercase letters
- Contains lowercase letters
- Contains numbers
4. Confirm your password
5. Click **Create Administrator Account**
</Steps>
With `strict_password_checking` disabled, you'll be warned if you choose a
weak password, but not prevented from doing so.
You'll now be taken to a login screen and can enter the credentials you just created.
### Adding and Modifying Users
If you are logged in as Administrator, you can add additional users. Click on the small "person silhouette" icon at the bottom left of the main Invoke screen and select "User Management"
![Administrator Menu](./assets/admin-add-user-1.png)
This will take you to the User Management screen...
![User Management screen](./assets/admin-add-user-2.png)
...where you can click "Create User" to add a new user.
![Add User Screen](./assets/admin-add-user-3.png)
The User Management screen also allows you to:
<Steps>
1. Temporarily change a user's status to Inactive, preventing them from logging in to Invoke.
2. Edit a user (by clicking on the pencil icon) to change the user's display name or password.
3. Permanently delete a user.
4. Grant a user Administrator privileges.
</Steps>
---
## Logging in as a Non-Administrative User
If you are a registered user on the system, enter your email address and password to log in. The Administrator will be able to provide you with the values to use:
![Login Screen](./assets/user-login-1.png)
As an unprivileged user you can do pretty much anything that's allowed under single-user mode — generating images, using LoRAs, creating and running workflows, creating image boards — but you are restricted against installing new models, changing low-level server settings, or interfering with other users. More information on user roles is given below.
### Changing your Profile
To change your display name or profile, click on the person silhouette icon at the bottom left of the screen and choose "My Profile". This will take you to a screen that lets you change these values. At this time you can change your display name but not your login ID (ordinarily your contact email address).
---
## Understanding User Roles
In single-user mode, you have access to all features without restrictions. In multi-user mode, InvokeAI has two user roles:
### Regular User
As a regular user, you can:
- Create and manage your own image boards
- Generate images using all AI tools (Linear, Canvas, Upscale, Workflows)
- Create, save, and load your own workflows
- View the full details of jobs you own on the session queue
- View redacted information for jobs being run by other users
- Customize your UI preferences (theme, hotkeys, etc.)
- View available models (read-only access to Model Manager)
- View shared and public boards created by other users
- View and use workflows marked as shared by other users
You cannot:
- Add, delete, or modify models
- View or modify other users' private boards, images, or workflows
- Manage user accounts
- Access system configuration
- Cancel other users' generation jobs
:::tip[The generation queue]
When two or more users are accessing InvokeAI at the same time, their image generation jobs share a single session queue. By default the server schedules jobs **round-robin** across users: each user gets one turn before anyone gets a second turn. This means a user who enqueues a large batch can no longer monopolize the queue — your jobs are interleaved with theirs rather than waiting for their entire batch to drain first. (An administrator can switch the server back to strict first-come, first-serve ordering; see the admin guide.)
While other users' jobs are running you will see the shared image generation progress bar, and the queue badge shows **`your jobs / all jobs`** — for example `2/5` means 2 of the 5 pending-or-in-progress jobs are yours. (In single-user mode the badge shows just a single total.)
Open the Queue tab to see where your job sits in relation to the other queued tasks.
:::
### Administrator
Administrators have all regular user capabilities, plus:
- Full model management (add, delete, configure models)
- Create and manage user accounts
- View and manage all users' generation queues
- View and manage all users' boards, images, and workflows (including system-owned legacy content)
- Access system configuration
- Grant or revoke admin privileges
---
## Working with Your Content in Multi-User Mode
### Image Boards
In multi-user mode, each user can create an unlimited number of boards and organize their images and assets as they see fit. Boards have three visibility levels:
- **Private** (default): Only you (and administrators) can see and modify the board.
- **Shared**: All users can view the board and its contents, but only you (and administrators) can modify it (rename, archive, delete, or add/remove images).
- **Public**: All users can view the board. Only you (and administrators) can modify the board's structure (rename, archive, delete).
To change a board's visibility, right-click on the board and select the desired visibility option.
Administrators can see and manage all users' image boards and their contents regardless of visibility settings.
### Going From Multi-User to Single-User Mode
If an InvokeAI instance was in multiuser mode and then restarted in single user mode (by setting `multiuser: false` in the configuration file), all users' boards will be consolidated in one place. Any images that were in "Uncategorized" will be merged together into a single Uncategorized board. If, at a later date, the server is restarted in multi-user mode, the boards and images will be assigned to the internal 'system' user. Admins can access this legacy content, and will not be restored to original owners.
### Workflows
Each user has their own private workflow library. Workflows you create are visible only to you by default.
You can share a workflow with other users by marking it as **shared** (public). Shared workflows appear in all users' workflow libraries and can be opened by anyone, but only the owner (or an administrator) can modify or delete them.
To share a workflow, open it and use the sharing controls to toggle its public/shared status.
:::caution[Preexisting workflows after enabling multi-user mode]
When you enable multi-user mode for the first time on an existing InvokeAI installation, all workflows that were created before multi-user mode was activated will appear in the **shared workflows** section. These preexisting workflows are owned by the internal "system" account and are visible to all users. Administrators can edit or delete these shared legacy workflows. Regular users can view and use them but cannot modify them.
:::
### The Generation Queue
The queue shows your pending and running generation tasks.
**Queue Features:**
- View your current and completed generations
- Cancel pending tasks
- Re-run previous generations
- Monitor progress in real-time
**Queue Isolation:**
- You will see your own queue items, as well as the items generated by other users, but the generation parameters (e.g. prompts) for other users' jobs are hidden for privacy reasons.
- Administrators can view all queues for troubleshooting.
- Your generations won't interfere with other users' tasks.
---
## Customizing Your Experience
### Personal Preferences
Your UI preferences are saved to your account and are restored when you log in:
- **Hotkeys**: Customize keyboard shortcuts
- **Canvas Settings**: Default zoom, grid visibility, etc.
- **Generation Defaults**: Default values for width, height, steps, etc.
These settings are stored per-user and won't affect other users.
---
## Troubleshooting
### Cannot Log In
**Issue:** Login fails with "Incorrect email or password"
**Solutions:**
- Verify you're entering the correct email address
- Check that Caps Lock is off
- Try typing the password slowly to avoid mistakes
- Contact your administrator if you've forgotten your password
**Issue:** Login fails with "Account is disabled"
**Solution:** Contact your administrator to reactivate your account
### Session Expired
**Issue:** You're suddenly logged out and see "Session expired"
**Explanation:** Sessions expire after 24 hours (or 7 days with "remember me")
**Solution:** Simply log in again with your credentials
### Cannot Access Features
**Issue:** Features like Model Manager show "Admin privileges required"
**Explanation:** Some features are restricted to administrators
**Solution:**
- For model viewing: You can view but not modify models
- For user management: Contact an administrator
- For system configuration: Contact an administrator
### Missing Boards or Images
**Issue:** Boards or images you created are not visible
**Possible Causes:**
<Steps>
1. **Filter Applied:** Check if a filter is hiding content
2. **Wrong User:** Ensure you're logged in with the correct account
3. **Archived Board:** Check the "Show Archived" option
</Steps>
**Solution:**
- Clear any active filters
- Verify you're logged in as the right user
- Check archived items
### Slow Performance
**Issue:** Generation or UI feels slower than expected
**Possible Causes:**
- Other users generating images simultaneously
- Server resource limits
- Network latency
**Solutions:**
- Check the queue to see if others are generating
- Wait for current generations to complete
- Contact administrator if persistent
### Generation Stuck in Queue
**Issue:** Your generation is queued but not starting
**Possible Causes:**
- Server is processing other users' generations
- Server resources are fully utilized
- Technical issue with the server
**Solutions:**
- Wait for your turn in the queue
- Check if your generation is paused
- Contact administrator if stuck for extended period
---
## Frequently Asked Questions
### Can other users see my images?
Not unless you change your board's visibility to "shared" or "public". All personal boards and images are private by default.
### Can I share my workflows with others?
Yes. You can mark any workflow as shared (public), which makes it visible to all users. Other users can view and use shared workflows, but only you or an administrator can modify or delete them.
### How long do sessions last?
- 24 hours by default
- 7 days if you check "Remember me" during login
### Can I use the API with multi-user mode?
Yes, but you'll need to authenticate with a JWT token. See the [API Guide](./api-guide/) for details.
### What happens if I forget my password?
Contact your administrator. They can reset your password for you.
### Can I have multiple sessions?
Yes, you can log in from multiple devices or browsers simultaneously. All sessions will use the same account and see the same content.
### Why can't I see the Model Manager "Add Models" tab?
Regular users can see the Models tab but with read-only access. Check that you're logged in and try refreshing the page.
### How do I know if I'm an administrator?
Click the user icon near the bottom of the left-hand navigation bar to open the user menu. If you are an administrator, an "Admin" badge appears under your name in that menu and a "User Management" item is shown alongside the usual Profile and Logout actions.
### Can I request admin privileges?
Yes, ask your current administrator to grant you admin privileges. Admin privileges will give you the ability to see all other users' boards and images, as well as to add models and change various server-wide settings.
## Getting Help
### Support Channels
- **Administrator:** Contact your system administrator for account issues
- **Documentation:** Check the [FAQ](/troubleshooting/faq/) for common issues
- **Community:** Join the [Discord](https://discord.gg/ZmtBAhwWhy) for help
- **Bug Reports:** File issues on [GitHub](https://github.com/invoke-ai/InvokeAI/issues)
### Reporting Issues
When reporting an issue, include:
- Your role (regular user or administrator)
- What you were trying to do
- What happened instead
- Any error messages you saw
- Your browser and operating system
## Additional Resources
- [Administrator Guide](./admin-guide/) — For administrators managing users and the system
- [API Guide](./api-guide/) — For developers using the InvokeAI API
@@ -0,0 +1,170 @@
---
title: Adding Nodes
description: Learn how to add, connect, and configure nodes in InvokeAI's workflow editor.
sidebar:
order: 3
lastUpdated: 2026-03-16
---
import { Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
Nodes are the building blocks of workflows. Each node performs a specific operation — loading a model, generating noise, applying conditioning, denoising latents, and more. By adding nodes to the canvas and connecting them together, you create a complete image generation pipeline.
## Opening the Node Picker
The node picker is a searchable command palette that lists every available node. There are three ways to open it:
<CardGrid>
<Card title="Keyboard Shortcut" icon="laptop">
Press <kbd>Shift</kbd> + <kbd>A</kbd> or <kbd>Space</kbd> while the workflow editor is focused.
</Card>
<Card title="Add Node Button" icon="add-document">
Click the **+** button in the top-left corner of the canvas.
</Card>
<Card title="Drag from a Port" icon="right-arrow">
Drag a connection from any input or output port and release it over empty canvas. The picker will open with results **filtered to compatible nodes only**.
</Card>
</CardGrid>
## Finding a Node
When the node picker opens, you can immediately start typing to search. The search is fuzzy and matches against several properties of each node:
- **Title** — the display name (e.g. "Denoise Latents")
- **Type** — the internal identifier
- **Description** — a short summary of what the node does
- **Tags** — category keywords
- **Node Pack** — the origin module (e.g. `invokeai` for built-in nodes, or a community pack name)
Each entry in the picker shows:
- A **classification badge** indicating stability — _Stable_, _Beta_ (yellow), _Prototype_ (red), or _Special_ (green)
- The **node title** and **node pack** name
- A brief **description**
Click a node or press <kbd>Enter</kbd> to add it to the canvas. The node will be placed near the center of your current viewport, or at your cursor position if you opened the picker by dragging from a port.
:::tip
If you opened the picker by dragging from a port, the list is automatically filtered to show only nodes that have a compatible input or output for that connection. This is a fast way to discover which nodes work together.
:::
## Special Nodes
In addition to invocation nodes (which perform image generation operations), the picker includes two special utility nodes:
<CardGrid>
<Card title="Notes" icon="pencil">
A sticky-note text area for documenting your workflow. Useful for leaving yourself reminders or explaining sections of a complex graph to others.
</Card>
<Card title="Current Image" icon="seti:image">
Displays the current image being generated or the most recent output. Helpful for monitoring progress in long workflows.
</Card>
</CardGrid>
## Connecting Nodes
Nodes have **input ports** on their left edge and **output ports** on their right edge. Ports are color-coded by data type so you can quickly identify compatible connections.
<Steps>
1. **Drag from an output port** on one node toward the canvas.
2. **Drop onto a compatible input port** on another node. Compatible ports will remain highlighted; incompatible ports will appear greyed out.
3. A **bezier edge** is drawn between the two ports, representing the data flow.
</Steps>
:::note
You can also drop a connection onto a node without targeting a specific port. InvokeAI will automatically connect to the **first compatible port** it finds on that node.
:::
### Connection Rules
- Connections must be between compatible data types (matching colors).
- A node cannot connect to itself.
- Each input port accepts only one connection (but an output can connect to many inputs).
- Connections snap within a 30px radius of a port for easy targeting.
### Reconnecting and Removing Edges
- **Reconnect** an edge by dragging it from its current port to a new one.
- **Remove** an edge by dragging it away from its port and releasing it over empty canvas.
- **Delete** selected edges with <kbd>Delete</kbd> or <kbd>Backspace</kbd>.
### Connectors
Connectors are small editor-only nodes that exist purely to **reroute edges** for a cleaner-looking graph. They are saved with the workflow but are flattened out of the graph before execution, so the runtime never sees them — you cannot use them to add logic, only to tidy wiring.
Ways to add a connector:
- **Right-click empty canvas → Add Connector**, then drag connections to and from it.
- **Double-click an existing edge** to insert a connector at that point, splicing it in.
Other behaviors worth knowing:
- **Target-first wiring works.** You can connect a connector's output to a downstream target field *before* hooking up its upstream source. The connector stays unresolved until a compatible source is connected; incompatible upstreams are rejected.
- **Type compatibility is enforced** through the connector, exactly as for normal edges.
- **Deleting a connector splices through** any edges that pass through it:
- `1 → 1`: the source is reconnected directly to the target.
- `1 → N`: the source is reconnected to every compatible downstream target.
- `1 → 0`: the connector is removed, no edges created.
- If a splice-through would produce an invalid graph, **Delete Connector** is disabled.
- **Connectors persist** across workflow save / load.
## Configuring Nodes
Once a node is on the canvas, you can configure it by editing its input fields directly. Each node exposes a set of fields specific to its function — for example, a noise node has a **Seed** field, while a model loader has a **Model** selector.
- **Inline editing** — Click on any input field to edit its value directly on the node.
- **Renaming** — Right-click a node's title or any input label to rename it.
- **Use Cache** — Toggle the caching option in the node footer to reuse previously computed values and speed up repeat runs.
- **Collapse** — Click the collapse button on the node header to minimize it, keeping the canvas tidy.
## Managing Nodes
Use these shortcuts to work efficiently with nodes on the canvas:
| Action | Shortcut |
| :--- | :--- |
| Add Node | <kbd>Shift</kbd> + <kbd>A</kbd> or <kbd>Space</kbd> |
| Copy | <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>C</kbd> |
| Paste | <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>V</kbd> |
| Paste with Edges | <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> |
| Select All | <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>A</kbd> |
| Delete | <kbd>Delete</kbd> or <kbd>Backspace</kbd> |
| Undo | <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>Z</kbd> |
| Redo | <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>Shift</kbd> + <kbd>Z</kbd> |
| Select Multiple | <kbd>Shift</kbd> + Click & Drag |
:::tip
All keyboard shortcuts are customizable. Open the Hotkeys modal with <kbd>Shift</kbd> + <kbd>?</kbd> to view or change any binding.
:::
## Adding to Linear View
Any input field on a node can be promoted to the **Linear View**, which provides a simplified UI for your workflow — perfect for sharing with others or for quick iteration.
<Steps>
1. Right-click on an **input label** on any node.
2. Select **"Add to Linear View"**.
3. The input now appears in the Linear View panel, where you can adjust it without navigating the full graph.
</Steps>
Custom names you set on input fields will carry over into the Linear View.
## Installing Community Nodes
InvokeAI's node system is extensible. Community-created nodes can add new capabilities to your workflows — from specialized image processing to LLM-powered prompt generation.
The easiest way to install a community node pack is through the **[Custom Node Manager](/features/workflows/custom-node-manager/)**: paste a Git URL in the **Nodes** sidebar tab and the pack is cloned, loaded, and made available without a restart.
If you prefer to install manually:
<Steps>
1. Find a node pack from the [Community Nodes](/features/workflows/community-nodes/) list.
2. Clone or download the node pack into the `nodes` folder inside your InvokeAI installation directory.
3. In the Custom Node Manager, click **Reload** (or restart InvokeAI). The new nodes will appear in the node picker.
</Steps>
:::note
`git clone` is preferred over downloading a ZIP — it makes it easy to update node packs later with `git pull`.
:::
For more details and a full catalog of available community nodes, see the [Community Nodes](/features/workflows/community-nodes/) page.
Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

@@ -0,0 +1,119 @@
---
title: ComfyUI Migration
lastUpdated: 2026-05-23
---
import { Card, CardGrid } from '@astrojs/starlight/components';
If you're coming to InvokeAI from ComfyUI, welcome! You'll find things are similar but different - the good news is that you already know how things should work, and it's just a matter of wiring them up!
<Card title="Node Granularity" icon="information">
InvokeAI's nodes tend to be more granular than default nodes in Comfy. This means each node in Invoke will do a specific task, and you might need to use multiple nodes to achieve the same result. The added granularity improves the control you have over your workflows.
</Card>
<Card title="Backend Differences" icon="puzzle">
InvokeAI's backend and ComfyUI's backend are very different, which means Comfy workflows are not able to be imported directly into InvokeAI. However, we have created a [list of popular workflows](../community-nodes) for you to get started with Nodes in InvokeAI!
</Card>
## Node Equivalents
Finding the right node is the hardest part of switching. Use the categories below to find the InvokeAI equivalents for the ComfyUI nodes you are used to.
### Sampling
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| KSampler | Denoise Latents |
| Ksampler Advanced | Denoise Latents |
### Loaders
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| Load Checkpoint | Main Model Loader _or_ SDXL Main Model Loader |
| Load VAE | VAE Loader |
| Load Lora | LoRA Loader _or_ SDXL Lora Loader |
| Load ControlNet Model | ControlNet |
| Load ControlNet Model (diff) | ControlNet |
| Load Style Model | Reference Only ControlNet will be coming in a future version of InvokeAI |
| unCLIPCheckpointLoader | N/A |
| GLIGENLoader | N/A |
| Hypernetwork Loader | N/A |
| Load Upscale Model | Occurs within "Upscale (RealESRGAN)" |
### Conditioning
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| CLIP Text Encode (Prompt) | Compel (Prompt) or SDXL Compel (Prompt) |
| CLIP Set Last Layer | CLIP Skip |
| Conditioning (Average) | Use the .blend() feature of prompts |
| Conditioning (Combine) | N/A |
| Conditioning (Concat) | See the Prompt Tools Community Node |
| Conditioning (Set Area) | N/A |
| Conditioning (Set Mask) | Mask Edge |
| CLIP Vision Encode | N/A |
| unCLIPConditioning | N/A |
| Apply ControlNet | ControlNet |
| Apply ControlNet (Advanced) | ControlNet |
### Latent
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| VAE Decode | Latents to Image |
| VAE Encode | Image to Latents |
| Empty Latent Image | Noise |
| Upscale Latent | Resize Latents |
| Upscale Latent By | Scale Latents |
| Latent Composite | Blend Latents |
| LatentCompositeMasked | N/A |
### Image
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| Save Image | Image |
| Preview Image | Current |
| Load Image | Image |
| Empty Image | Blank Image |
| Invert Image | Invert Lerp Image |
| Batch Images | Link "Image" nodes into an "Image Collection" node |
| Pad Image for Outpainting | Outpainting is easily accomplished in the Unified Canvas |
| ImageCompositeMasked | Paste Image |
| Upscale Image | Resize Image |
| Upscale Image By | Upscale Image |
| Upscale Image (using Model) | Upscale Image |
| ImageBlur | Blur Image |
| ImageQuantize | N/A |
| ImageSharpen | N/A |
| Canny | Canny Processor |
### Mask
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| Load Image (as Mask) | Image |
| Convert Mask to Image | Image |
| Convert Image to Mask | Image |
| SolidMask | N/A |
| InvertMask | Invert Lerp Image |
| CropMask | Crop Image |
| MaskComposite | Combine Mask |
| FeatherMask | Blur Image |
### Advanced
| ComfyUI Node | Invoke Equivalent |
| :--- | :--- |
| Load CLIP | Main Model Loader _or_ SDXL Main Model Loader |
| UNETLoader | Main Model Loader _or_ SDXL Main Model Loader |
| DualCLIPLoader | Main Model Loader _or_ SDXL Main Model Loader |
| Load Checkpoint | Main Model Loader _or_ SDXL Main Model Loader |
| ConditioningZeroOut | N/A |
| ConditioningSetTimestepRange | N/A |
| CLIPTextEncodeSDXLRefiner | Compel (Prompt) or SDXL Compel (Prompt) |
| CLIPTextEncodeSDXL | Compel (Prompt) or SDXL Compel (Prompt) |
| ModelMergeSimple | Model Merging is available in the Model Manager |
| ModelMergeBlocks | Model Merging is available in the Model Manager |
| CheckpointSave | Model saving is available in the Model Manager |
| CLIPMergeSimple | N/A |
@@ -0,0 +1,731 @@
---
title: Community Nodes
---
These are nodes that have been developed by the community, for the community. If you're not sure what a node is, you can learn more about nodes [here](/concepts/nodes-workflows/).
If you'd like to submit a node for the community, please refer to the [node creation overview](/development/guides/creating-nodes/).
To use a node, add the node to the `nodes` folder found in your InvokeAI install location.
The suggested method is to use `git clone` to clone the repository the node is found in. This allows for easy updates of the node in the future.
If you'd prefer, you can also just download the whole node folder from the linked repository and add it to the `nodes` folder.
To use a community workflow, download the `.json` node graph file and load it into Invoke AI via the **Load Workflow** button in the Workflow Editor.
---
### Anamorphic Tools
**Description:** A set of nodes to perform anamorphic modifications to images, like lens blur, streaks, spherical distortion, and vignetting.
**Node Link:** https://github.com/JPPhoto/anamorphic-tools
---
### Adapters Linked Nodes
**Description:** A set of nodes for linked adapters (ControlNet, IP-Adaptor & T2I-Adapter). This allows multiple adapters to be chained together without using a `collect` node which means it can be used inside an `iterate` node without any collecting on every iteration issues.
- `ControlNet-Linked` - Collects ControlNet info to pass to other nodes.
- `IP-Adapter-Linked` - Collects IP-Adapter info to pass to other nodes.
- `T2I-Adapter-Linked` - Collects T2I-Adapter info to pass to other nodes.
Note: These are inherited from the core nodes so any update to the core nodes should be reflected in these.
**Node Link:** https://github.com/skunkworxdark/adapters-linked-nodes
---
### Autostereogram Nodes
**Description:** Generate autostereogram images from a depth map. This is not a very practically useful node but more a 90s nostalgic indulgence as I used to love these images as a kid.
**Node Link:** https://github.com/skunkworxdark/autostereogram_nodes
**Example Usage:**
<img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider.png" width="200" /> -> <img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider-depth.png" width="200" /> -> <img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider-dots.png" width="200" /> <img src="https://raw.githubusercontent.com/skunkworxdark/autostereogram_nodes/refs/heads/main/images/spider-pattern.png" width="200" />
---
### Average Images
**Description:** This node takes in a collection of images of the same size and averages them as output. It converts everything to RGB mode first.
**Node Link:** https://github.com/JPPhoto/average-images-node
---
### BiRefNet Background Removal
**Description:** Remove image backgrounds using BiRefNet (Bilateral Reference Network), a high-quality segmentation model. Supports multiple model variants including standard, high-resolution, matting, portrait, and specialized models for different use cases.
**Node Link:** https://github.com/veeliks/invoke_birefnet
**Output Examples**
<section>
<img src="https://raw.githubusercontent.com/veeliks/invoke_birefnet/main/.readme/example_before_removal.png" width="49%" alt="Before background removal" />
<img src="https://raw.githubusercontent.com/veeliks/invoke_birefnet/main/.readme/example_after_removal.png" width="49%" alt="After background removal" />
</section>
---
### Clean Image Artifacts After Cut
Description: Removes residual artifacts after an image is separated from its background.
Node Link: https://github.com/VeyDlin/clean-artifact-after-cut-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/clean-artifact-after-cut-node/master/.readme/node.png" width="500" />
---
### Close Color Mask
Description: Generates a mask for images based on a closely matching color, useful for color-based selections.
Node Link: https://github.com/VeyDlin/close-color-mask-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/close-color-mask-node/master/.readme/node.png" width="500" />
---
### Clothing Mask
Description: Employs a U2NET neural network trained for the segmentation of clothing items in images.
Node Link: https://github.com/VeyDlin/clothing-mask-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/clothing-mask-node/master/.readme/node.png" width="500" />
---
### Contrast Limited Adaptive Histogram Equalization
Description: Enhances local image contrast using adaptive histogram equalization with contrast limiting.
Node Link: https://github.com/VeyDlin/clahe-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/clahe-node/master/.readme/node.png" width="500" />
---
### Curves
**Description:** Adjust an image's curve based on a user-defined string.
**Node Link:** https://github.com/JPPhoto/curves-node
---
### Depth Map from Wavefront OBJ
**Description:** Render depth maps from Wavefront .obj files (triangulated) using this simple 3D renderer utilizing numpy and matplotlib to compute and color the scene. There are simple parameters to change the FOV, camera position, and model orientation.
To be imported, an .obj must use triangulated meshes, so make sure to enable that option if exporting from a 3D modeling program. This renderer makes each triangle a solid color based on its average depth, so it will cause anomalies if your .obj has large triangles. In Blender, the Remesh modifier can be helpful to subdivide a mesh into small pieces that work well given these limitations.
**Node Link:** https://github.com/dwringer/depth-from-obj-node
**Example Usage:**
<img src="https://raw.githubusercontent.com/dwringer/depth-from-obj-node/main/depth_from_obj_usage.jpg" width="500" />
---
### Enhance Detail
**Description:** A single node that can enhance the detail in an image. Increase or decrease details in an image using a guided filter (as opposed to the typical Gaussian blur used by most sharpening filters.) Based on the `Enhance Detail` ComfyUI node from https://github.com/spacepxl/ComfyUI-Image-Filters
**Node Link:** https://github.com/skunkworxdark/enhance-detail-node
**Example Usage:**
<img src="https://raw.githubusercontent.com/skunkworxdark/enhance-detail-node/refs/heads/main/images/Comparison.png" />
---
### Film Grain
**Description:** This node adds a film grain effect to the input image based on the weights, seeds, and blur radii parameters. It works with RGB input images only.
**Node Link:** https://github.com/JPPhoto/film-grain-node
---
### Flip Pose
**Description:** This node will flip an openpose image horizontally, recoloring it to make sure that it isn't facing the wrong direction. Note that it does not work with openpose hands.
**Node Link:** https://github.com/JPPhoto/flip-pose-node
---
### Flux Ideal Size
**Description:** This node returns an ideal size to use for the first stage of a Flux image generation pipeline. Generating at the right size helps limit duplication and odd subject placement.
**Node Link:** https://github.com/JPPhoto/flux-ideal-size
---
### Generative Grammar-Based Prompt Nodes
**Description:** This set of 3 nodes generates prompts from simple user-defined grammar rules (loaded from custom files - examples provided below). The prompts are made by recursively expanding a special template string, replacing nonterminal "parts-of-speech" until no nonterminal terms remain in the string.
This includes 3 Nodes:
- *Lookup Table from File* - loads a YAML file "prompt" section (or of a whole folder of YAML's) into a JSON-ified dictionary (Lookups output)
- *Lookups Entry from Prompt* - places a single entry in a new Lookups output under the specified heading
- *Prompt from Lookup Table* - uses a Collection of Lookups as grammar rules from which to randomly generate prompts.
**Node Link:** https://github.com/dwringer/generative-grammar-prompt-nodes
**Example Usage:**
<img src="https://raw.githubusercontent.com/dwringer/generative-grammar-prompt-nodes/main/lookuptables_usage.jpg" width="500" />
---
### GPT2RandomPromptMaker
**Description:** A node for InvokeAI utilizes the GPT-2 language model to generate random prompts based on a provided seed and context.
**Node Link:** https://github.com/mickr777/GPT2RandomPromptMaker
**Output Examples**
Generated Prompt: An enchanted weapon will be usable by any character regardless of their alignment.
<img src="https://github.com/mickr777/InvokeAI/assets/115216705/8496ba09-bcdd-4ff7-8076-ff213b6a1e4c" width="200" />
---
### Grid to Gif
**Description:** One node that turns a grid image into an image collection, one node that turns an image collection into a gif.
**Node Link:** https://github.com/mildmisery/invokeai-GridToGifNode/blob/main/GridToGif.py
**Example Node Graph:** https://github.com/mildmisery/invokeai-GridToGifNode/blob/main/Grid%20to%20Gif%20Example%20Workflow.json
**Output Examples**
<img src="https://raw.githubusercontent.com/mildmisery/invokeai-GridToGifNode/main/input.png" width="300" />
<img src="https://raw.githubusercontent.com/mildmisery/invokeai-GridToGifNode/main/output.gif" width="300" />
---
### Halftone
**Description**: Halftone converts the source image to grayscale and then performs halftoning. CMYK Halftone converts the image to CMYK and applies a per-channel halftoning to make the source image look like a magazine or newspaper. For both nodes, you can specify angles and halftone dot spacing.
**Node Link:** https://github.com/JPPhoto/halftone-node
**Example**
Input:
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/fd5efb9f-4355-4409-a1c2-c1ca99e0cab4" width="300" />
Halftone Output:
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/7e606f29-e68f-4d46-b3d5-97f799a4ec2f" width="300" />
CMYK Halftone Output:
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/c59c578f-db8e-4d66-8c66-2851752d75ea" width="300" />
---
### Hand Refiner with MeshGraphormer
**Description**: Hand Refiner takes in your image and automatically generates a fixed depth map for the hands along with a mask of the hands region that will conveniently allow you to use them along with ControlNet to fix the wonky hands generated by Stable Diffusion
**Node Link:** https://github.com/blessedcoolant/invoke_meshgraphormer
**View**
<img src="https://raw.githubusercontent.com/blessedcoolant/invoke_meshgraphormer/main/assets/preview.jpg" />
---
### Image and Mask Composition Pack
**Description:** This is a pack of nodes for composing masks and images, including a simple text mask creator and both image and latent offset nodes. The offsets wrap around, so these can be used in conjunction with the Seamless node to progressively generate centered on different parts of the seamless tiling.
This includes 15 Nodes:
- *Adjust Image Hue Plus* - Rotate the hue of an image in one of several different color spaces.
- *Blend Latents/Noise (Masked)* - Use a mask to blend part of one latents tensor [including Noise outputs] into another. Can be used to "renoise" sections during a multi-stage [masked] denoising process.
- *Enhance Image* - Boost or reduce color saturation, contrast, brightness, sharpness, or invert colors of any image at any stage with this simple wrapper for pillow [PIL]'s ImageEnhance module.
- *Equivalent Achromatic Lightness* - Calculates image lightness accounting for Helmholtz-Kohlrausch effect based on a method described by High, Green, and Nussbaum (2023).
- *Text to Mask (Clipseg)* - Input a prompt and an image to generate a mask representing areas of the image matched by the prompt.
- *Text to Mask Advanced (Clipseg)* - Output up to four prompt masks combined with logical "and", logical "or", or as separate channels of an RGBA image.
- *Image Layer Blend* - Perform a layered blend of two images using alpha compositing. Opacity of top layer is selectable, with optional mask and several different blend modes/color spaces.
- *Image Compositor* - Take a subject from an image with a flat backdrop and layer it on another image using a chroma key or flood select background removal.
- *Image Dilate or Erode* - Dilate or expand a mask (or any image!). This is equivalent to an expand/contract operation.
- *Image Value Thresholds* - Clip an image to pure black/white beyond specified thresholds.
- *Offset Latents* - Offset a latents tensor in the vertical and/or horizontal dimensions, wrapping it around.
- *Offset Image* - Offset an image in the vertical and/or horizontal dimensions, wrapping it around.
- *Rotate/Flip Image* - Rotate an image in degrees clockwise/counterclockwise about its center, optionally resizing the image boundaries to fit, or flipping it about the vertical and/or horizontal axes.
- *Shadows/Highlights/Midtones* - Extract three masks (with adjustable hard or soft thresholds) representing shadows, midtones, and highlights regions of an image.
- *Text Mask (simple 2D)* - create and position a white on black (or black on white) line of text using any font locally available to Invoke.
**Node Link:** https://github.com/dwringer/composition-nodes
<img src="https://raw.githubusercontent.com/dwringer/composition-nodes/main/composition_pack_overview.jpg" width="500" />
---
### Image Dominant Color
Description: Identifies and extracts the dominant color from an image using k-means clustering.
Node Link: https://github.com/VeyDlin/image-dominant-color-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/image-dominant-color-node/master/.readme/node.png" width="500" />
---
### Image Export
**Description:** Export images in multiple formats (AVIF, JPEG, PNG, TIFF, WebP) with format-specific compression and quality options.
**Node Link:** https://github.com/veeliks/invoke_image_export
**Nodes:**
<section>
<img src="https://raw.githubusercontent.com/veeliks/invoke_image_export/main/.readme/node_avif.png" width="19%" alt="Save Image as AVIF" />
<img src="https://raw.githubusercontent.com/veeliks/invoke_image_export/main/.readme/node_jpeg.png" width="19%" alt="Save Image as JPEG" />
<img src="https://raw.githubusercontent.com/veeliks/invoke_image_export/main/.readme/node_png.png" width="19%" alt="Save Image as PNG" />
<img src="https://raw.githubusercontent.com/veeliks/invoke_image_export/main/.readme/node_tiff.png" width="19%" alt="Save Image as TIFF" />
<img src="https://raw.githubusercontent.com/veeliks/invoke_image_export/main/.readme/node_webp.png" width="19%" alt="Save Image as WebP" />
</section>
---
### Image to Character Art Image Nodes
**Description:** Group of nodes to convert an input image into ascii/unicode art Image
**Node Link:** https://github.com/mickr777/imagetoasciiimage
**Output Examples**
<img src="https://user-images.githubusercontent.com/115216705/271817646-8e061fcc-9a2c-4fa9-bcc7-c0f7b01e9056.png" width="300" />
<img src="https://github.com/mickr777/imagetoasciiimage/assets/115216705/3c4990eb-2f42-46b9-90f9-0088b939dc6a" width="300" />
<img src="https://github.com/mickr777/imagetoasciiimage/assets/115216705/fee7f800-a4a8-41e2-a66b-c66e4343307e" width="300" />
<img src="https://github.com/mickr777/imagetoasciiimage/assets/115216705/1d9c1003-a45f-45c2-aac7-46470bb89330" width="300" />
---
### Image Picker
**Description:** This InvokeAI node takes in a collection of images and randomly chooses one. This can be useful when you have a number of poses to choose from for a ControlNet node, or a number of input images for another purpose.
**Node Link:** https://github.com/JPPhoto/image-picker-node
---
### Image Resize Plus
Description: Provides various image resizing options such as fill, stretch, fit, center, and crop.
Node Link: https://github.com/VeyDlin/image-resize-plus-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/image-resize-plus-node/master/.readme/node.png" width="500" />
---
### Latent Upscale
**Description:** This node uses a small (~2.4mb) model to upscale the latents used in a Stable Diffusion 1.5 or Stable Diffusion XL image generation, rather than the typical interpolation method, avoiding the traditional downsides of the latent upscale technique.
**Node Link:** [https://github.com/gogurtenjoyer/latent-upscale](https://github.com/gogurtenjoyer/latent-upscale)
---
### Load Video Frame
**Description:** This is a video frame image provider + indexer/video creation nodes for hooking up to iterators and ranges and ControlNets and such for invokeAI node experimentation. Think animation + ControlNet outputs.
**Node Link:** https://github.com/helix4u/load_video_frame
**Output Example:**
<img src="https://raw.githubusercontent.com/helix4u/load_video_frame/refs/heads/main/_git_assets/dance1736978273.gif" width="500" />
---
### Make 3D
**Description:** Create compelling 3D stereo images from 2D originals.
**Node Link:** [https://gitlab.com/srcrr/shift3d/-/raw/main/make3d.py](https://gitlab.com/srcrr/shift3d)
**Example Node Graph:** https://gitlab.com/srcrr/shift3d/-/raw/main/example-workflow.json?ref_type=heads&inline=false
**Output Examples**
<img src="https://gitlab.com/srcrr/shift3d/-/raw/main/example-1.png" width="300" />
<img src="https://gitlab.com/srcrr/shift3d/-/raw/main/example-2.png" width="300" />
---
### Mask Operations
Description: Offers logical operations (OR, SUB, AND) for combining and manipulating image masks.
Node Link: https://github.com/VeyDlin/mask-operations-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/mask-operations-node/master/.readme/node.png" width="500" />
---
### Match Histogram
**Description:** An InvokeAI node to match a histogram from one image to another. This is a bit like the `color correct` node in the main InvokeAI but this works in the YCbCr colourspace and can handle images of different sizes. Also does not require a mask input.
- Option to only transfer luminance channel.
- Option to save output as grayscale
A good use case for this node is to normalize the colors of an image that has been through the tiled scaling workflow of my XYGrid Nodes.
See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/main/README.md
**Node Link:** https://github.com/skunkworxdark/match_histogram
**Output Examples**
<img src="https://github.com/skunkworxdark/match_histogram/assets/21961335/ed12f329-a0ef-444a-9bae-129ed60d6097" />
---
### Metadata Linked Nodes
**Description:** A set of nodes for Metadata. Collect Metadata from within an `iterate` node & extract metadata from an image.
- `Metadata Item Linked` - Allows collecting of metadata while within an iterate node with no need for a collect node or conversion to metadata node
- `Metadata From Image` - Provides Metadata from an image
- `Metadata To String` - Extracts a String value of a label from metadata
- `Metadata To Integer` - Extracts an Integer value of a label from metadata
- `Metadata To Float` - Extracts a Float value of a label from metadata
- `Metadata To Scheduler` - Extracts a Scheduler value of a label from metadata
- `Metadata To Bool` - Extracts Bool types from metadata
- `Metadata To Model` - Extracts model types from metadata
- `Metadata To SDXL Model` - Extracts SDXL model types from metadata
- `Metadata To LoRAs` - Extracts Loras from metadata.
- `Metadata To SDXL LoRAs` - Extracts SDXL Loras from metadata
- `Metadata To ControlNets` - Extracts ControNets from metadata
- `Metadata To IP-Adapters` - Extracts IP-Adapters from metadata
- `Metadata To T2I-Adapters` - Extracts T2I-Adapters from metadata
- `Denoise Latents + Metadata` - This is an inherited version of the existing `Denoise Latents` node but with a metadata input and output.
**Node Link:** https://github.com/skunkworxdark/metadata-linked-nodes
---
### Negative Image
Description: Creates a negative version of an image, effective for visual effects and mask inversion.
Node Link: https://github.com/VeyDlin/negative-image-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/negative-image-node/master/.readme/node.png" width="500" />
---
### Nightmare Promptgen
**Description:** Nightmare Prompt Generator - Uses a local text generation model to create unique imaginative (but usually nightmarish) prompts for InvokeAI. By default, it allows you to choose from some gpt-neo models I finetuned on over 2500 of my own InvokeAI prompts in Compel format, but you're able to add your own, as well. Offers support for replacing any troublesome words with a random choice from list you can also define.
**Node Link:** [https://github.com/gogurtenjoyer/nightmare-promptgen](https://github.com/gogurtenjoyer/nightmare-promptgen)
---
### Ollama Node
**Description:** Uses Ollama API to expand text prompts for text-to-image generation using local LLMs. Works great for expanding basic prompts into detailed natural language prompts for Flux. Also provides a toggle to unload the LLM model immediately after expanding, to free up VRAM for Invoke to continue the image generation workflow.
**Node Link:** https://github.com/Jonseed/Ollama-Node
**Example Node Graph:** https://github.com/Jonseed/Ollama-Node/blob/main/Ollama-Node-Flux-example.json
**View:**
![ollama node](https://raw.githubusercontent.com/Jonseed/Ollama-Node/a3e7cdc55e394cb89c1ea7ed54e106c212c85e8c/ollama-node-screenshot.png)
---
### One Button Prompt
<img src="https://raw.githubusercontent.com/AIrjen/OneButtonPrompt_X_InvokeAI/refs/heads/main/images/background.png" width="800" />
**Description:** an extensive suite of auto prompt generation and prompt helper nodes based on extensive logic. Get creative with the best prompt generator in the world.
The main node generates interesting prompts based on a set of parameters. There are also some additional nodes such as Auto Negative Prompt, One Button Artify, Create Prompt Variant and other cool prompt toys to play around with.
**Node Link:** [https://github.com/AIrjen/OneButtonPrompt_X_InvokeAI](https://github.com/AIrjen/OneButtonPrompt_X_InvokeAI)
**Nodes:**
<img src="https://raw.githubusercontent.com/AIrjen/OneButtonPrompt_X_InvokeAI/refs/heads/main/images/OBP_nodes_invokeai.png" width="800" />
---
### Oobabooga
**Description:** asks a local LLM running in Oobabooga's Text-Generation-Webui to write a prompt based on the user input.
**Link:** https://github.com/sammyf/oobabooga-node
**Example:**
"describe a new mystical creature in its natural environment"
*can return*
"The mystical creature I am describing to you is called the "Glimmerwing". It is a majestic, iridescent being that inhabits the depths of the most enchanted forests and glimmering lakes. Its body is covered in shimmering scales that reflect every color of the rainbow, and it has delicate, translucent wings that sparkle like diamonds in the sunlight. The Glimmerwing's home is a crystal-clear lake, surrounded by towering trees with leaves that shimmer like jewels. In this serene environment, the Glimmerwing spends its days swimming gracefully through the water, chasing schools of glittering fish and playing with the gentle ripples of the lake's surface.
As the sun sets, the Glimmerwing perches on a branch of one of the trees, spreading its wings to catch the last rays of light. The creature's scales glow softly, casting a rainbow of colors across the forest floor. The Glimmerwing sings a haunting melody, its voice echoing through the stillness of the night air. Its song is said to have the power to heal the sick and bring peace to troubled souls. Those who are lucky enough to hear the Glimmerwing's song are forever changed by its beauty and grace."
<img src="https://github.com/sammyf/oobabooga-node/assets/42468608/cecdd820-93dd-4c35-abbf-607e001fb2ed" width="300" />
**Requirement**
a Text-Generation-Webui instance (might work remotely too, but I never tried it) and obviously InvokeAI 3.x
**Note**
This node works best with SDXL models, especially as the style can be described independently of the LLM's output.
---
### Prompt Tools
**Description:** A set of InvokeAI nodes that add general prompt (string) manipulation tools. Designed to accompany the `Prompts From File` node and other prompt generation nodes.
1. `Prompt To File` - saves a prompt or collection of prompts to a file. one per line. There is an append/overwrite option.
2. `PTFields Collect` - Converts image generation fields into a Json format string that can be passed to Prompt to file.
3. `PTFields Expand` - Takes Json string and converts it to individual generation parameters. This can be fed from the Prompts to file node.
4. `Prompt Strength` - Formats prompt with strength like the weighted format of compel
5. `Prompt Strength Combine` - Combines weighted prompts for .and()/.blend()
6. `CSV To Index String` - Gets a string from a CSV by index. Includes a Random index option
The following Nodes are now included in v3.2 of Invoke and are no longer in this set of tools.
- `Prompt Join` -> `String Join`
- `Prompt Join Three` -> `String Join Three`
- `Prompt Replace` -> `String Replace`
- `Prompt Split Neg` -> `String Split Neg`
See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/main/README.md
**Node Link:** https://github.com/skunkworxdark/Prompt-tools-nodes
**Workflow Examples**
<img src="https://raw.githubusercontent.com/skunkworxdark/prompt-tools/refs/heads/main/images/CSVToIndexStringNode.png"/>
---
### Remote Image
**Description:** This is a pack of nodes to interoperate with other services, be they public websites or bespoke local servers. The pack consists of these nodes:
- *Load Remote Image* - Lets you load remote images such as a realtime webcam image, an image of the day, or dynamically created images.
- *Post Image to Remote Server* - Lets you upload an image to a remote server using an HTTP POST request, eg for storage, display or further processing.
**Node Link:** https://github.com/fieldOfView/InvokeAI-remote_image
---
### BriaAI Remove Background
**Description**: Implements one click background removal with BriaAI's new version 1.4 model which seems to be producing better results than any other previous background removal tool.
**Node Link:** https://github.com/blessedcoolant/invoke_bria_rmbg
**View**
<img src="https://raw.githubusercontent.com/blessedcoolant/invoke_bria_rmbg/main/assets/preview.jpg" />
---
### Remove Background
Description: An integration of the rembg package to remove backgrounds from images using multiple U2NET models.
Node Link: https://github.com/VeyDlin/remove-background-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/remove-background-node/master/.readme/node.png" width="500" />
---
### Retroize
**Description:** Retroize is a collection of nodes for InvokeAI to "Retroize" images. Any image can be given a fresh coat of retro paint with these nodes, either from your gallery or from within the graph itself. It includes nodes to pixelize, quantize, palettize, and ditherize images; as well as to retrieve palettes from existing images.
**Node Link:** https://github.com/Ar7ific1al/invokeai-retroizeinode/
**Retroize Output Examples**
<img src="https://github.com/Ar7ific1al/InvokeAI_nodes_retroize/assets/2306586/de8b4fa6-324c-4c2d-b36c-297600c73974" width="500" />
---
### Stereogram Nodes
**Description:** A set of custom nodes for InvokeAI to create cross-view or parallel-view stereograms. Stereograms are 2D images that, when viewed properly, reveal a 3D scene. Check out [r/crossview](https://www.reddit.com/r/CrossView/) for tutorials.
**Node Link:** https://github.com/simonfuhrmann/invokeai-stereo
**Example Workflow and Output**
<img src="https://raw.githubusercontent.com/simonfuhrmann/invokeai-stereo/refs/heads/main/docs/example_promo_03.jpg" width="600" />
---
### Simple Skin Detection
Description: Detects skin in images based on predefined color thresholds.
Node Link: https://github.com/VeyDlin/simple-skin-detection-node
View:
<img src="https://raw.githubusercontent.com/VeyDlin/simple-skin-detection-node/master/.readme/node.png" width="500" />
---
### Size Stepper Nodes
**Description:** This is a set of nodes for calculating the necessary size increments for doing upscaling workflows. Use the *Final Size & Orientation* node to enter your full size dimensions and orientation (portrait/landscape/random), then plug that and your initial generation dimensions into the *Ideal Size Stepper* and get 1, 2, or 3 intermediate pairs of dimensions for upscaling. Note this does not output the initial size or full size dimensions: the 1, 2, or 3 outputs of this node are only the intermediate sizes.
A third node is included, *Random Switch (Integers)*, which is just a generic version of Final Size with no orientation selection.
**Node Link:** https://github.com/dwringer/size-stepper-nodes
**Example Usage:**
<img src="https://raw.githubusercontent.com/dwringer/size-stepper-nodes/main/size_nodes_usage.jpg" width="500" />
---
### Text font to Image
**Description:** text font to text image node for InvokeAI, download a font to use (or if in font cache uses it from there), the text is always resized to the image size, but can control that with padding, optional 2nd line
**Node Link:** https://github.com/mickr777/textfontimage
**Output Examples**
<img src="https://github.com/mickr777/InvokeAI/assets/115216705/c21b0af3-d9c6-4c16-9152-846a23effd36" width="300" />
Results after using the depth controlnet
<img src="https://github.com/mickr777/InvokeAI/assets/115216705/915f1a53-968e-43eb-aa61-07cd8f1a733a" width="300" />
<img src="https://github.com/mickr777/InvokeAI/assets/115216705/821ef89e-8a60-44f5-b94e-471a9d8690cc" width="300" />
<img src="https://github.com/mickr777/InvokeAI/assets/115216705/2befcb6d-49f4-4bfd-b5fc-1fee19274f89" width="300" />
---
### Thresholding
**Description:** This node generates masks for highlights, midtones, and shadows given an input image. You can optionally specify a blur for the lookup table used in making those masks from the source image.
**Node Link:** https://github.com/JPPhoto/thresholding-node
**Examples**
Input:
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/c88ada13-fb3d-484c-a4fe-947b44712632" width="300" />
Highlights/Midtones/Shadows:
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/727021c1-36ff-4ec8-90c8-105e00de986d" width="300" />
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/0b721bfc-f051-404e-b905-2f16b824ddfe" width="300" />
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/04c1297f-1c88-42b6-a7df-dd090b976286" width="300" />
Highlights/Midtones/Shadows (with LUT blur enabled):
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/19aa718a-70c1-4668-8169-d68f4bd13771" width="300" />
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/0a440e43-697f-4d17-82ee-f287467df0a5" width="300" />
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/0701fd0f-2ca7-4fe2-8613-2b52547bafce" width="300" />
---
### Unsharp Mask
**Description:** Applies an unsharp mask filter to an image, preserving its alpha channel in the process.
**Node Link:** https://github.com/JPPhoto/unsharp-mask-node
---
### XY Image to Grid and Images to Grids nodes
**Description:** These nodes add the following to InvokeAI:
- Generate grids of images from multiple input images
- Create XY grid images with labels from parameters
- Split images into overlapping tiles for processing (for super-resolution workflows)
- Recombine image tiles into a single output image blending the seams
The nodes include:
1. `Images To Grids` - Combine multiple images into a grid of images
2. `XYImage To Grid` - Take X & Y params and creates a labeled image grid.
3. `XYImage Tiles` - Super-resolution (embiggen) style tiled resizing
4. `Image Tot XYImages` - Takes an image and cuts it up into a number of columns and rows.
5. Multiple supporting nodes - Helper nodes for data wrangling and building `XYImage` collections
See full docs here: https://github.com/skunkworxdark/XYGrid_nodes/edit/main/README.md
**Node Link:** https://github.com/skunkworxdark/XYGrid_nodes
**Output Examples**
<img src="https://raw.githubusercontent.com/skunkworxdark/XYGrid_nodes/refs/heads/main/images/collage.png" />
---
### Example Node Template
**Description:** This node allows you to do super cool things with InvokeAI.
**Node Link:** https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/app/invocations/prompt.py
**Example Workflow:** https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Prompt_from_File.json
**Output Examples**
<img src="https://invoke-ai.github.io/InvokeAI/assets/invoke_ai_banner.png" width="500" />
## Disclaimer
The nodes linked have been developed and contributed by members of the Invoke AI community. While we strive to ensure the quality and safety of these contributions, we do not guarantee the reliability or security of the nodes. If you have issues or concerns with any of the nodes below, please raise it on GitHub or in the Discord.
## Help
If you run into any issues with a node, please post in the [InvokeAI Discord](https://discord.gg/ZmtBAhwWhy).
@@ -0,0 +1,76 @@
---
title: Custom Node Manager
sidebar:
order: 5
---
import { Steps } from '@astrojs/starlight/components';
The Custom Node Manager installs, updates, and removes community node packs directly from the InvokeAI UI — no manual file copying, no restart required.
## Opening the Custom Node Manager
Click the **Nodes** tab (circuit icon) in the left sidebar, between **Models** and **Queue**.
The page is split into two panels:
- **Left:** the list of installed node packs, with each pack's node count, type badges, and on-disk path.
- **Right:** the install UI, with tabs for **Git Repository URL** and **Scan Folder**, plus an install log at the bottom.
## Installing a node pack
<Steps>
1. On the right panel, choose the **Git Repository URL** tab.
2. Paste the Git URL of the pack, e.g. `https://github.com/user/my-node-pack.git`.
3. Click **Install**.
</Steps>
What happens during install:
- The repo is cloned into your `nodes` directory.
- The nodes are loaded into the running InvokeAI process immediately — **no restart needed**.
- Any workflow `.json` files found in the repo are imported into your workflow library and tagged with `node-pack:<pack-name>` so you can filter for them.
- The install log at the bottom of the panel shows the result for each step.
:::caution[Security]
Custom nodes execute arbitrary Python on your machine. **Only install node packs from authors you trust.** A malicious pack could harm your system or exfiltrate data.
:::
### Python dependencies
The Custom Node Manager **does not** automatically run `pip install` for a pack's `requirements.txt` or `pyproject.toml`. Auto-installing into the running InvokeAI environment risks pulling in incompatible package versions and breaking the application.
If a pack ships extra dependencies, you'll see a warning toast after installation. Install them yourself — typically `pip install -r requirements.txt` from inside an activated InvokeAI environment, but check the pack's README first. After installing, click **Reload** so the new dependencies take effect.
## Managing installed packs
Each entry in the left panel has actions for managing the pack:
- **Reload** — re-scans the `nodes` directory. Use this after manually adding a pack via `git clone`, or after installing extra Python dependencies.
- **Uninstall** — removes the pack from disk, unregisters its nodes from the running process, and removes any workflows that were imported from the pack. No restart needed.
## Scan Folder tab
The **Scan Folder** tab shows the path of your `nodes` directory. Anything placed there manually (for example, by `git clone`-ing a pack directly) is detected automatically at startup. Use **Reload** to pick up packs added at runtime.
## Troubleshooting
### Install fails
- Confirm the Git URL is correct and reachable.
- The repo must contain an `__init__.py` at its root.
- Read the install log — it surfaces the underlying error.
### Nodes don't appear after install
- Click **Reload**.
- Check that the pack's `__init__.py` imports the node classes.
- Check the server console for import errors.
### Workflows show errors after uninstalling
User-created workflows that reference nodes from an uninstalled pack will show errors for the missing node types. Either reinstall the pack or remove the affected nodes from the workflow.
## Authoring a node pack
If you want to publish your own pack so it can be installed by URL, see the [Creating a Node Pack](/development/guides/creating-nodes/) developer guide for the required repository layout, `__init__.py` requirements, and conventions for shipping workflows alongside your nodes.
@@ -0,0 +1,141 @@
---
title: Editor Interface
description: Learn how to use the Workflow Editor in InvokeAI.
sidebar:
order: 2
lastUpdated: 2026-02-20
---
import { Card, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
The workflow editor is a blank canvas allowing for the use of individual functions and image transformations to control the image generation workflow. Nodes take in inputs on the left side of the node, and return an output on the right side of the node.
A node graph is composed of multiple nodes that are connected together to create a workflow. Nodes' inputs and outputs are connected by dragging connectors from node to node. Inputs and outputs are color-coded for ease of use.
:::tip[New to Diffusion?]
If you're not familiar with Diffusion, take a look at our [Diffusion Overview](../../concepts/diffusion). Understanding how diffusion works will enable you to more easily use the Workflow Editor and build workflows to suit your needs.
:::
## Features
<Card title="Workflow Library" icon="open-book">
Save workflows to the Invoke database, allowing you to easily create, modify, and share workflows as needed. A curated set of default workflows is provided to help explain important node usage.
![Workflow Library](./assets/workflow_library.png)
The library has two views:
- **Browse Workflows** lists curated default workflows, filterable by a fixed set of category tags.
- **Your Workflows** lists workflows you have saved. The tag filter here is **dynamic** — it shows every unique tag found across your own workflows, with a count per tag.
Add comma-separated tags to a workflow (e.g. `portrait, SDXL, upscaling`) when saving it. The tags appear at the bottom of each workflow tile in the library and become selectable filters in the sidebar. Click one or more tags to narrow the list; click **Your Workflows** to clear the filter and show everything again. Tag counts update automatically when you create, edit, or delete a workflow.
</Card>
<Card title="Linear View" icon="list-format">
Create a custom UI for your workflow, making it easier to iterate on your generations. The Linear UI View is saved alongside the workflow, allowing you to share workflows and enable others to use them.
<Steps>
1. Right-click on any **input label** on a node.
2. Select **"Add to Linear View"**.
3. The input will now appear in your Linear View panel!
</Steps>
![Linear View](./assets/linearview.png)
</Card>
<Card title="Renaming Fields and Nodes" icon="pencil">
Any node or input field can be renamed in the workflow editor. If the input field you have renamed has been added to the Linear View, the changed name will be reflected in both places.
</Card>
<Card title="Node Caching" icon="rocket">
Nodes have a **"Use Cache"** option in their footer. This allows for performance improvements by reusing previously cached values during workflow processing.
</Card>
### Managing Nodes
Use these quick keyboard shortcuts to navigate and manage your workflow efficiently:
<CardGrid>
<Card title="Copy Node" icon="document">
<kbd>Ctrl</kbd> + <kbd>C</kbd> (or <kbd>Cmd</kbd> + <kbd>C</kbd>)
</Card>
<Card title="Paste Node" icon="approve-check-circle">
<kbd>Ctrl</kbd> + <kbd>V</kbd> (or <kbd>Cmd</kbd> + <kbd>V</kbd>)
</Card>
<Card title="Select Multiple" icon="list-format">
<kbd>Shift</kbd> + Click & Drag
</Card>
<Card title="Delete Node" icon="close">
<kbd>Backspace</kbd> / <kbd>Delete</kbd>
</Card>
</CardGrid>
## Important Nodes & Concepts
There are several node grouping concepts that can be examined with a narrow focus. These (and other) groupings can be pieced together to make up functional graph setups, and are important to understanding how groups of nodes work together as part of a whole.
:::note
The screenshots below aren't examples of complete functioning node graphs, but rather snippets demonstrating specific concepts.
:::
<Tabs>
<TabItem label="Noise & Conditioning" icon="setting">
### Create Latent Noise
An initial noise tensor is necessary for the latent diffusion process. As a result, the Denoising node requires a noise node input.
The standard **Create Latent Noise** node now includes a **Noise Type** selector for architecture-specific latent
shapes. Leave it at **SD** for classic 4-channel Stable Diffusion workflows, or switch it to the architecture that
matches the downstream denoiser when working with models like FLUX, FLUX.2, SD3, CogView4, Z-Image, or Anima.
![Create Latent Noise](./assets/groupsnoise.png)
### Text Prompt Conditioning
Conditioning is necessary for the latent diffusion process, whether empty or not. As a result, the Denoising node requires positive and negative conditioning inputs. Conditioning is reliant on a CLIP text encoder provided by the Model Loader node.
![Text Prompt Conditioning](./assets/groupsconditioning.png)
</TabItem>
<TabItem label="Image Processing" icon="seti:image">
### Image to Latents & VAE
The **ImageToLatents** node takes in a pixel image and a VAE and outputs latents. The **LatentsToImage** node does the opposite, taking in latents and a VAE and outputs a pixel image.
![Image to Latents & VAE](./assets/groupsimgvae.png)
### Scaling
Use the **ImageScale**, **ScaleLatents**, and **Upscale** nodes to upscale images and/or latent images. Upscaling is the process of enlarging an image and adding more detail.
The chosen method differs across contexts. However, be aware that latents are already noisy and compressed at their original resolution; scaling an image could produce more detailed results.
![Scaling Nodes](./assets/groupsallscale.png)
</TabItem>
<TabItem label="Advanced Control" icon="puzzle">
### ControlNet
The **ControlNet** node outputs a Control, which can be provided as input to a Denoise Latents node. Depending on the type of ControlNet desired, ControlNet nodes usually require an image processor node, such as a Canny Processor or Depth Processor, which prepares an input image for use with ControlNet.
![ControlNet Setup](./assets/groupscontrol.png)
### LoRA
The **Lora Loader** node lets you load a LoRA and pass it as output. A LoRA provides fine-tunes to the UNet and text encoder weights that augment the base models image and text vocabularies.
![LoRA Setup](./assets/groupslora.png)
</TabItem>
<TabItem label="Iteration & Batching" icon="list-format">
### Defined & Random Seeds
It is common to want to use both the same seed (for continuity) and random seeds (for variety). To define a seed, simply enter it into the **'Seed'** field on a noise node. Conversely, the **RandomInt** node generates a random integer between 'Low' and 'High', and can be used as input to the 'Seed' edge point on a noise node to randomize your seed.
![Defined & Random Seeds](./assets/groupsnoise.png)
### Iteration + Multiple Images as Input
Iteration is a common concept in any processing, and means to repeat a process with given input. In nodes, you're able to use the **Iterate** node to iterate through collections usually gathered by the **Collect** node.
The Iterate node has many potential uses, from processing a collection of images one after another, to varying seeds across multiple image generations and more. This screenshot demonstrates how to collect several images and use them in an image generation workflow.
![Iteration](./assets/groupsiterate.png)
### Batch / Multiple Image Generation
Batch or multiple image generation in the workflow editor is done using the **RandomRange** node. In this case, the 'Size' field represents the number of images to generate, meaning this example will generate 4 images.
As RandomRange produces a collection of integers, we need to add the Iterate node to iterate through the collection. This noise can then be fed to the Denoise Latents node for it to iterate through the denoising process with the different seeds provided.
![Batch Generation](./assets/groupsmultigenseeding.png)
</TabItem>
</Tabs>

Some files were not shown because too many files have changed in this diff Show More