How AUTOMATIC1111 Extensions and Scripts Work: A Complete Development Guide
AUTOMATIC1111 extensions are optional packages loaded from the extensions/ directory that add UI tabs and models, while scripts are Python plugins subclassing modules.scripts.Script that inject controls into txt2img/img2img; both are auto-discovered at runtime via modules/extensions.py and modules/scripts.py using metadata.ini for dependency management.
The AUTOMATIC1111/stable-diffusion-webui repository provides a modular plugin system allowing developers to extend the Stable Diffusion interface without modifying core code. Understanding how AUTOMATIC1111 extensions and scripts function enables you to add custom processing pipelines, new interface tabs, and specialized post-processing effects. This guide examines the exact loading mechanisms in modules/extensions.py and modules/scripts.py to show you how to build and register your own components.
Understanding the Extension Architecture
Extensions in the WebUI are self-contained packages that live in the extensions/ folder (or the built-in extensions-builtin/). The core loader in modules/extensions.py maintains global lists named extensions and extension_paths that track every discovered package.
How Extensions Are Discovered
When the application starts, list_extensions() in modules/extensions.py scans both extensions-builtin/ and extensions/ directories. For each folder found:
- It instantiates
ExtensionMetadataby reading themetadata.inifile, parsing fields likeName,Requires,Before, andAfter. - It creates an
Extensionobject storing the path, repository information, and enabled flag. - It validates dependencies—if an extension lists
Requires = another-extensionand that dependency is missing or disabled, the loader aborts loading the dependent extension.
The UI renders these packages in the Extensions tab via modules/ui_extensions.py, where users can toggle enable flags and apply changes to trigger a reload.
How Scripts Integrate with the UI
While extensions add broad functionality, scripts provide granular control within the generation pipeline. They are defined in modules/scripts.py and implement the Script base class.
The Script Loading Pipeline
The load_scripts() function orchestrates discovery through this exact sequence:
- Enumeration:
list_scripts("scripts", ".py")first scans the corescripts/folder, then appends anyscripts/sub-folders found inside active extensions usingext.list_files. - Dependency Resolution: For each script file, the system creates a
ScriptWithDependenciesobject. It readsrequires,load_before, andload_afterfrom the parent extension'smetadata.ini. - Topological Sorting: The loader calls
util.topological_sortto resolve the load order, ensuring scripts with dependencies load after their requirements while preventing circular references. - Instantiation: The ordered list of
ScriptFileobjects is imported viascript_loading.load_module. Every class subclassingmodules.scripts.Scriptis instantiated and stored inscripts_dataorpostprocessing_scripts_data.
Script Execution and Callbacks
The ScriptRunner class in modules/scripts.py manages runtime behavior. When creating the UI, create_script_ui records argument index ranges (args_from, args_to) so the runner can slice p.script_args when invoking callbacks. Scripts can implement several lifecycle methods:
run(self, p, *args)– Executed when the user selects the script from the dropdown and clicks Generate.before_process(self, p, *args)– Called before the diffusion process begins; allows modifying prompts or parameters.process(self, p, *args)– Called during processing.postprocess(self, p, processed, *args)– Called after image generation; allows modifying the finalprocessedobject.
For Always-on scripts that remain visible in the UI regardless of dropdown selection, implement show(self, is_img2img) to return scripts.AlwaysVisible.
Creating a Custom Extension
To create a new extension, you need a folder with a metadata.ini file and optional Python modules.
Required Folder Structure
Create a directory under extensions/my-cool-ext/ with the following layout:
extensions/
└─ my-cool-ext/
├─ metadata.ini
├─ ui.py # optional: adds a new tab
└─ scripts/
└─ hello_world.py # optional: custom scripts
The metadata.ini File
The metadata.ini file is mandatory for the loader to recognize your package. Place it in the extension root:
[Extension]
Name = My Cool Extension
Requires = another-extension
Before = some-extension
After = other-extension
- Name: Display name in the Extensions tab.
- Requires: Comma-separated list of extension names that must be present and enabled.
- Before/After: Hints for load order relative to other extensions.
Adding a UI Tab
To register a new Gradio tab, create ui.py in your extension folder:
# extensions/my-cool-ext/ui.py
import gradio as gr
from modules import ui_extensions
def tab_content():
with gr.Column():
gr.Markdown("## My Extension Tab")
gr.Button("Execute", elem_id="my_btn")
# Register when the extension loads
ui_extensions.register_ui_tab(name="My Cool Extension", func=tab_content)
Creating a Custom Script
Scripts live in the scripts/ directory (either the root scripts/ folder or inside an extension's scripts/ sub-folder). Each script must define a class inheriting from modules.scripts.Script.
Basic Script Example
Create scripts/hello_world.py:
from modules import scripts, shared
import gradio as gr
class HelloWorld(scripts.Script):
"""Adds a text input and logs messages during generation."""
def title(self):
return "Hello World"
def ui(self, is_img2img):
# Components appear when this script is selected
self.msg = gr.Textbox(label="Message", value="Hello from script!")
return [self.msg]
def run(self, p, msg):
# p is the StableDiffusionProcessing object
shared.log.info(f"[HelloWorld] User message: {msg}")
# Return None to continue with normal generation
return None
The title() method determines the dropdown label. The ui() method returns a list of Gradio components whose values are passed as arguments to run().
Always-On Script Example
For scripts that should always appear in the interface:
# scripts/always_on_example.py
from modules import scripts
import gradio as gr
class AlwaysOnExample(scripts.Script):
def title(self):
return "Always-On Example"
def show(self, is_img2img):
# Makes UI always visible
return scripts.AlwaysVisible
def ui(self, is_img2img):
self.factor = gr.Slider(0.1, 3.0, step=0.1, label="Scale Factor")
return [self.factor]
def before_process(self, p, factor):
# Modify the prompt before generation
p.prompt = f"{p.prompt} ++scale:{factor}"
Post-Processing Script Example
To manipulate images after generation:
# scripts/invert_colors.py
from modules import scripts, shared
from PIL import Image
import numpy as np
import gradio as gr
class InvertColors(scripts.Script):
def title(self):
return "Invert Colors"
def show(self, is_img2img):
return scripts.AlwaysVisible
def ui(self, is_img2img):
self.enable = gr.Checkbox(label="Enable Inversion", value=False)
return [self.enable]
def postprocess(self, p, processed, enable):
if not enable:
return
# processed.images is a list of PIL Images
processed.images = [
Image.fromarray(255 - np.array(img))
for img in processed.images
]
shared.log.info("[InvertColors] Colors inverted")
Managing Dependencies and Load Order
Both extensions and scripts use the same dependency system defined in metadata.ini.
Dependency Resolution
When modules/extensions.py parses metadata.ini, it validates the Requires field to ensure dependencies exist and are enabled. For scripts, the loader reads these same keys from their parent extension's metadata to build the ScriptWithDependencies graph.
Load Order Control
The Before and After keys in metadata.ini determine initialization sequence. The loader passes these constraints to util.topological_sort, which produces a deterministic load order. This prevents initialization errors when one script patches functionality that another script depends on.
If a circular dependency is detected, the loader raises an error and prevents the UI from starting, ensuring system stability.
Summary
- Extensions are directory-based packages discovered by
modules/extensions.pyvialist_extensions(), requiring ametadata.inifile to declare metadata and dependencies. - Scripts are Python classes subclassing
modules.scripts.Script, loaded bymodules/scripts.pyusing topological sorting to resolveRequires,Before, andAfterconstraints. - The
ScriptRunnerclass manages script lifecycle, injecting Gradio UI components and routing arguments to callbacks likerun(),before_process(), andpostprocess(). - Always-on scripts return
scripts.AlwaysVisiblefromshow()to remain persistently visible in the txt2img/img2img interface. - Extensions can add new UI tabs by calling
ui_extensions.register_ui_tab()in aui.pyfile within the extension folder.
Frequently Asked Questions
What is the difference between an extension and a script in AUTOMATIC1111?
An extension is a complete package that may contain multiple scripts, UI tabs, models, and JavaScript, defined by a folder with metadata.ini. A script is a single Python file containing a Script subclass that appears in the Scripts dropdown or as an always-on panel. Extensions are managed at the repository level, while scripts are managed individually within the generation interface.
Where should I place my custom script files?
Place standalone scripts in the root scripts/ directory. If your script belongs to a specific extension, place it inside that extension's scripts/ sub-folder (e.g., extensions/my-ext/scripts/my_script.py). The loader in modules/scripts.py automatically discovers both locations when load_scripts() runs at startup.
How do I make my script always visible in the interface?
Override the show(self, is_img2img) method in your Script subclass to return scripts.AlwaysVisible instead of the default True. This causes the Gradio components from your ui() method to appear persistently in the txt2img or img2img panels, allowing users to toggle features without selecting the script from the dropdown.
Can my extension depend on another extension being installed?
Yes. In your extension's metadata.ini, add a Requires key listing the required extension names separated by commas (e.g., Requires = controlnet, sd-webui-deforum). The loader validates these dependencies during list_extensions() in modules/extensions.py and will disable your extension if requirements are missing, logging the conflict to the console.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →