Forge your imagination into reality with ImageSmith - A powerful Discord bot that seamlessly integrates with ComfyUI for intuitive image, video, and audio generation.
Note: ImageSmith is a workflow executor, not a workflow creator. You'll need to create your own workflows, but the bot makes them easily accessible through Discord's UI. Check out the example workflows in the repository to get started.
- Quick Start
- User Guide
- Configuration
- Security
- Internationalization
- Extending
- Development
- License
- Acknowledgments
- Disclaimer
- Community
You need a running ComfyUI instance and a Discord Bot Token.
Important: Default workflows use
sd_xl_base_1.0for image generation and Mochi models for video generation. Ensure these are available in your ComfyUI instance.
docker run -e DISCORD_TOKEN="<your_discord_token>" \
# Optional: mount your own configuration file
# --mount type=bind,source=./configuration.yml,target=/app/configuration.yml \
# Optional: mount a custom workflows directory
# -v "./custom_workflows:/app/custom_workflows" \
ghcr.io/jtyszkiew/imagesmith:latestgit clone https://github.com/jtyszkiew/ImageSmith.git
cd ImageSmith
python -m venv venv
source venv/bin/activate # Windows: .\venv\Scripts\activate
pip install -r requirements.txt
cp configuration.example.yml configuration.yml
# Edit configuration.yml with your settings
python main.pyImageSmith exposes slash commands for generating images, videos, and audio from text prompts or existing images. The output type depends on the ComfyUI workflow — the bot automatically detects whether the workflow produces images, video (gifs), or audio and sends the appropriate file to Discord.
Command names are configurable — see Custom Command Names.
| Command | Description | Parameters |
|---|---|---|
/forge |
Generate from text | prompt, [workflow], [settings] |
/reforge |
Transform existing image | image, prompt, [workflow], [settings] |
/upscale |
Upscale with modifications | image, prompt, [workflow], [settings] |
/workflows |
List available workflows | - |
# Basic generation
/forge A majestic mountain landscape at sunset
# Using specific workflow
/forge A cyberpunk city --workflow cyberpunk_generator
# With custom settings
/forge A fantasy character --workflow character_generator --settings "change_steps(30);add_lora('fantasy_style', 0.8)"
All configuration lives in configuration.yml. Copy configuration.example.yml as a starting point.
The comfyui block configures how the bot connects to one or more ComfyUI instances.
comfyui:
instances:
- url: 'http://127.0.0.1:8188'
weight: 1
- url: 'https://comfyui-2.example.com:8188'
weight: 2
auth:
username: admin
password: secret
timeout: 120
- url: 'https://comfyui-3.example.com:8188'
auth:
api_key: your-api-key-here
ssl_verify: true
ssl_cert: /path/to/ca-bundle.pem
input_dir: /path/to/comfyui/input
show_node_updates: true
load_balancer:
strategy: LEAST_BUSYEach instance can have its own auth block. Authentication methods are checked in this order:
- API Key — if
api_keyis set, sends aBearertoken header on both HTTP and WebSocket connections - Basic Auth — if
usernameandpasswordare set (and noapi_key), sends a Basic auth header on HTTP connections
| Key | Type | Description |
|---|---|---|
api_key |
string | Bearer token for API key authentication |
username |
string | Username for HTTP Basic authentication |
password |
string | Password for HTTP Basic authentication |
ssl_verify |
bool | Verify TLS certificates (default: true) |
ssl_cert |
string | Path to a custom CA bundle PEM file |
Workflows map Discord commands to ComfyUI workflow JSON files. Each workflow is a named entry under the workflows key.
workflows:
forge:
type: txt2img
description: "Generate realistic images"
workflow: "./workflows/txt2img.json"
text_prompt_node_id: "6"
text_prompt_input_key: "text"
default: true
selectable: true
reforge:
type: img2img
description: "Regenerate images"
workflow: "./workflows/img2img.json"
text_prompt_node_id: "6"
image_input_node_id: "10"
default: true
txt2vid:
type: txt2img
description: "Generate short video"
workflow: "./workflows/txt2vid.json"
text_prompt_node_id: "6"| Key | Type | Description |
|---|---|---|
type |
string | txt2img, img2img, or upscale |
description |
string | Shown in the /workflows listing |
workflow |
string | Path to the ComfyUI workflow JSON file |
text_prompt_node_id |
string | Node ID in the workflow JSON to inject the text prompt into |
text_prompt_input_key |
string | Input field name within the prompt node (default: "text") |
image_input_node_id |
string | Node ID to inject the uploaded image into (img2img/upscale types) |
default |
bool | Global default for its type — the first match wins |
selectable |
bool | If false, hides from /workflows and the workflow dropdown (default: true) |
Video and audio workflows use txt2img as the type. The bot automatically detects the output format (image, gif/video, or audio) from what ComfyUI returns.
When multiple ComfyUI instances are configured, the bot distributes work across them.
comfyui:
load_balancer:
strategy: LEAST_BUSY| Strategy | Description |
|---|---|
LEAST_BUSY |
Picks the instance with the lowest ratio of active generations to weight (default) |
ROUND_ROBIN |
Cycles through connected instances in order |
RANDOM |
Picks a random instance, weighted by each instance's weight value |
The weight field on each instance (default: 1) affects both RANDOM (probability weight) and LEAST_BUSY (busyness denominator). A higher weight means the instance receives proportionally more work.
Settings are Python functions that modify the workflow JSON before it is sent to ComfyUI. They are defined per-workflow.
Two special names are reserved:
__before— runs automatically before any user-specified settings__after— runs automatically after all user-specified settings
workflows:
forge:
settings:
- name: __before
description: "Default workflow configuration"
code: |
def __before(workflowjson):
import random
workflowjson["4"]["inputs"]["ckpt_name"] = "Juggernaut_X_RunDiffusion.safetensors"
workflowjson["3"]["inputs"]["seed"] = random.randint(0, 2**32 - 1)
- name: hd
description: "HD resolution preset"
code: |
def hd(workflowjson):
workflowjson["5"]["inputs"]["width"] = 1280
workflowjson["5"]["inputs"]["height"] = 720Users invoke settings in their command: /forge A fantasy character --settings "hd()"
Multiple settings can be chained with semicolons: "hd();add_lora('fantasy_style', 0.8)"
Forms collect interactive input from users via Discord modals or select menus before a workflow runs.
workflows:
forge:
form:
- name: seed
type: text
required: false
description: Seed for the model
message: Provide a seed you would like to use
on_submit: |
def on_submit(workflowjson, value):
workflowjson["65"]["inputs"]["seed"] = value
on_default: |
def on_default(workflowjson):
import random
workflowjson["65"]["inputs"]["seed"] = random.randint(0, 2**32 - 1)| Type | Input method | Value passed to on_submit |
|---|---|---|
text |
Modal text input | Integer (parsed from input) |
textarea |
Modal text input (multi-line) | String (raw input) |
resolution |
Select dropdown | List of two integers [width, height] (max 2048) |
select |
Select dropdown (multi-select) | List of strings |
| Key | Type | Description | Required |
|---|---|---|---|
name |
string | Name of the form field | Yes |
type |
string | text, textarea, resolution, or select |
Yes |
required |
bool | Whether the field is required | No |
description |
string | Displayed as the modal title or dropdown placeholder | No |
message |
string | Displayed as the input label inside the modal | No |
on_submit |
string | Python code executed when the form is submitted. Function name must be on_submit. |
Yes |
on_default |
string | Python code executed when a non-required field has no input. Function name must be on_default. |
No |
options |
list | List of {name, value} objects for select and resolution types |
No |
- name: style
type: select
required: true
description: Choose art style
message: Select one or more styles
options:
- name: Realistic
value: realistic
- name: Anime
value: anime
- name: Oil Painting
value: oil_painting
on_submit: |
def on_submit(workflowjson, value):
workflowjson["3"]["inputs"]["style"] = ", ".join(value)- name: resolution
type: resolution
required: true
description: Output resolution
message: Select a resolution
options:
- name: 512x512
value: 512x512
- name: 1024x1024
value: 1024x1024
- name: 1280x720
value: 1280x720
on_submit: |
def on_submit(workflowjson, value):
workflowjson["5"]["inputs"]["width"] = value[0]
workflowjson["5"]["inputs"]["height"] = value[1]Rename the default slash commands to whatever you prefer:
commands:
forge: "generate"
reforge: "remix"
upscale: "enhance"
workflows: "list"Any key omitted keeps its default name. For example, to only rename /forge to /generate:
commands:
forge: "generate"comfyui:
show_node_updates: falseWhen show_node_updates is set to false, per-node "Processing node X..." messages and progress bars are hidden. Completion, error, and media messages still appear normally. The default is true.
You can set a workflow as the default for specific channels or users. When a user runs a command without specifying a workflow, the bot checks (in order):
- A workflow whose
default_for.userslist contains the user's name - A workflow whose
default_for.channelslist contains the channel's name - A workflow with
default: truefor that command type - The first workflow of the matching type
workflows:
anime_forge:
type: txt2img
description: "Anime-style generation"
workflow: "./workflows/anime_txt2img.json"
text_prompt_node_id: "6"
default_for:
channels:
- anime-art
- anime-general
users:
- anime_fan_123Restrict who can use a workflow by adding a security block:
workflows:
forge:
security:
enabled: true
allowed_roles:
- "Artist"
- "Admin"
allowed_users:
- "trusteduser"
allowed_channels:
- "image-generation"| Key | Type | Description |
|---|---|---|
enabled |
bool | Enable access control for this workflow (default: false) |
allowed_roles |
list | Discord role names that can use this workflow |
allowed_users |
list | Discord usernames that can use this workflow |
allowed_channels |
list | Channel names where this workflow can be used |
When enabled is true, all three checks are ANDed together — the user must satisfy every non-empty list. If a list is empty or omitted, that check is skipped.
Individual settings within a workflow can also have their own access control:
workflows:
forge:
settings:
- name: hd
description: "HD resolution preset"
security:
enabled: true
allowed_roles:
- "Premium"
allowed_users:
- "admin_user"
code: |
def hd(workflowjson):
workflowjson["5"]["inputs"]["width"] = 1280
workflowjson["5"]["inputs"]["height"] = 720The same allowed_roles, allowed_users, and allowed_channels keys are available at the setting level.
ImageSmith includes built-in support for multiple languages. All UI strings — embeds, buttons, status messages, errors, forms, and security messages — are fully translatable.
| Code | Language |
|---|---|
| — | English (default) |
de |
German |
es |
Spanish |
fr |
French |
ja |
Japanese |
pl |
Polish |
pt |
Portuguese |
language: "pl"You can also override individual string keys without creating a language file:
i18n:
embed:
titles:
error: "Custom Error Title"
bot:
starting_generation: "Generating your image..."Strings are resolved in three layers (later layers override earlier):
i18n.yml— built-in English defaultsi18n.<language>.yml— language file (loaded whenlanguageis set)i18nblock inconfiguration.yml— inline overrides
See i18n.yml for the full list of available string keys.
Setting env: "dev" in your configuration causes raw error messages to be shown instead of generic error text. The default is "prod".
Plugins are Python classes that extend the bot's functionality. Place them in the plugins/ directory — they are auto-discovered and loaded on startup.
from src.core.plugin import Plugin
class MyPlugin(Plugin):
async def on_load(self):
await super().on_load()
self.bot.hook_manager.register_hook('is.comfyui.client.before_create', self.my_hook)
async def my_hook(self, workflow_json: dict, instances: list):
return workflow_jsonEvery plugin receives self.bot — the ImageSmith instance — which provides access to:
bot.hook_manager— register and fire hooksbot.workflow_manager— access workflows and configurationbot.comfy_client— the ComfyUI clientbot.form_manager— register custom form field handlers
| Hook | When it fires |
|---|---|
is.comfyui.client.before_create |
Before the ComfyUI client is created |
is.comfyui.client.after_create |
After the client is created and connected |
is.comfyui.client.instance.timeout |
When an idle instance times out |
is.comfyui.client.instance.reconnect |
Before attempting to reconnect an instance |
is.security.before |
Before the security check runs |
is.security |
During the security check (return SecurityResult to allow/deny) |
pip install pytest pytest-asyncio pytest-mock pytest-cov
pytest tests/ -v --cov=./- Fork the repository
- Create feature branch:
git checkout -b feature/AmazingFeature - Commit changes:
git commit -m 'Add AmazingFeature' - Push to branch:
git push origin feature/AmazingFeature - Open a Pull Request
Licensed under the MIT License - see the LICENSE file for details.
- ComfyUI - Image generation backend
- discord.py - Discord integration
This bot is for educational and creative purposes. Users are responsible for ensuring their usage complies with ComfyUI's and Discord's terms of service.
Join our Discord server to see the bot in action and stay updated with the latest developments!
