A Practical Guide to Blender Add-on UI Development

Turn your Blender scripts into real tools artists love using—here’s how to build clean, intuitive UI panels for your add-ons.

2 minutes ago   •   9 min read

By Basile Samel
Photo by Jakub Żerdzicki / Unsplash
📄
Turn your Blender scripts into real tools artists love using—here’s how to build clean, intuitive UI panels for your add-ons.

If you’ve ever written a Blender script, you’ve probably realized that getting the feature right is only half the battle: the other half is getting someone else to use it! A clean user interface is a must to share and sell Blender add-ons.

In this guide, you’ll learn how to build user interfaces for your Blender add-ons using the built-in layout system. We’ll cover the most common types of UI components, where panels can appear, and walk through a minimal working example. By the end, you’ll know how to give your add-on a Blender-native graphical interface.

💡
Looking for working examples?

You can find the complete source code for the example integration showcased in this guide on our GitHub:

🔗 https://github.com/cgwire/blender-ui-addon-script

1. Common UI Components

In Blender, every element of the user interface has its equivalent in the Python library. You build UI by creating classes that inherit from one of the following types:

  • bpy.types.Panel - for custom panels (the most common)
  • bpy.types.Menu - for menus and submenus
  • bpy.types.Operator - for actions or tools that can be run from buttons

Each of these classes can implement a draw(self, context) method where you describe what the interface should look like using layout commands. Blender’s layout system handles the spacing, alignment, and positioning automatically: it's a declarative UI system where you just describe what should appear and in what order.

Here are the most common layout elements you’ll use:

Basic Display Elements

  • Label - Displays plain, non-interactive text. Format: layout.label(text="Hello!")
  • Separator - Adds vertical space between items for readability. Format: layout.separator()

Buttons, Inputs, Props, and Operators

  • Operator Button - Creates a clickable button that triggers an operator (a function registered as a Blender command). You can use this for actions like exporting, duplicating, or running a custom script. Syntax: layout.operator("myaddon.some_action", text="Run Action")

The layout.prop() method is used to display editable Blender properties which are either built-in data (like context.object) or your own custom properties. For example, layout.prop(context.object, "name") shows an editable text field for the object’s name. Blender automatically chooses the right widget (text box, slider, checkbox, etc.) based on the property’s type:

  • Checkbox (Boolean property) - Displays a toggle checkbox. Example: layout.prop(context.object, "hide_viewport")
  • Number Field / Slider (Float or Int) - Displays a numeric input, often with a slider. Example: layout.prop(context.object, "location", index=0, text="X Location")
  • Dropdown Menu (Enum property) - Displays a dropdown list when the property is an EnumProperty. Example: layout.prop(context.object, "type")
  • Text Input - Displays a text box for string properties. Example: layout.prop(my_settings, "username")

Organizing the Layout

To keep your UI structured and easy to understand, Blender provides layout containers like rows, columns, and boxes.

A panel contains rows and columns. Rows and columns contain properties, operators, and labels. Blender automatically handles padding, alignment, and scaling to match the theme and layout rules.

  • A row (horizontal grouping) puts elements next to each other horizontally:
row = layout.row()
row.prop(obj, "location")
row.prop(obj, "rotation_euler")
  • A column (vertical grouping) stacks elements vertically:
col = layout.column()
col.prop(obj, "scale")
col.prop(obj, "dimensions")
  • box (Visual grouping) draws a bordered box that visually groups related controls, like sections:
box = layout.box()
box.label(text="Transform Settings")
box.prop(obj, "location")
box.prop(obj, "rotation_euler")

For the full list of UI components, have a look at the User Interface page of the official Blender documentation.


2. Where You Can Put UI Panels

When you create a custom panel in Blender, you can decide where in the interface it appears and what region it occupies with two key class attributes:

  • bl_space_type - which editor or workspace your panel belongs to (for example, the 3D View, the Properties Editor, or the Node Editor).
  • bl_region_type - which part of that editor the panel appears in (for example, the sidebar, toolbar, or main window).

Here is a list of the most typical areas where you might place a custom panel:

  • The 3D view sidebar appears in the right-hand N-panel sidebar of the 3D Viewport. This is the most common location for modeling, rigging, or scene tools:
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'

  • You can add panels inside the Properties Editor, among the Object, Material, or Scene tabs. Use this when your add-on deals with materials, objects, render settings, or scene properties:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'

  • In the UV/Image Editor sidebar (useful for texture tools or image utilities):
bl_space_type = 'IMAGE_EDITOR'
bl_region_type = 'UI'

  • In the sidebar of the Shader, Geometry Node, or Compositor editors for tools that work with nodes, shaders, or procedural systems:
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'

The best panel location depends on your tool’s purpose:

  • Modeling / Object tools → 3D View sidebar (VIEW_3D + UI)
  • Material or render settings → Properties editor (PROPERTIES + WINDOW)
  • Texture utilities → Image editor sidebar (IMAGE_EDITOR + UI)
  • Shader / Geometry tools → Node editor sidebar (NODE_EDITOR + UI)

Picking the right space helps users find your add-on where they naturally expect to, keeping your UI consistent with Blender’s.


3. Minimal Example: Custom Panel in the 3D View Sidebar

Let's experiment with a simple plugin: a custom panel in the 3D view sidebar that displays a "hello world" text alert when clicking on a button.

1) bl_info - addon metadata

We start by specifying the add-on metadata to tell Blender how to present our add-on to a potential user:

bl_info = {
    "name": "Simple Addon Example",
    "author": "Your Name",
    "version": (1, 0),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar > Simple Tab",
    "description": "A simple example addon that prints a message",
    "category": "3D View",
}

  • bl_info is a module-level dictionary Blender uses to show addon info in Preferences → Add-ons
    • name: human-readable name shown in the list
    • author: author string
    • version: tuple representing addon version
    • blender: minimum Blender version this addon targets (tuple)
    • location: where the addon UI appears (helpful for users)
    • description: short description used in the UI
    • category: category grouping in the Add-ons list

It's essential to keep your bl_info accurate, as Blender reads it when scanning installed add-ons.

2) Define an operator class

We then define an Operator subclass. Operators are the official way to perform actions in Blender: they can be invoked from UI, shortcuts, search menu, etc.

class SIMPLEADDON_OT_hello(bpy.types.Operator):
    bl_idname = "simple_addon.say_hello"
    bl_label = "Say Hello"
    bl_description = "Prints a message to the console"

    def execute(self, context):
        self.report({'INFO'}, "Hello from Blender Addon!")
        print("Hello from Blender Addon!")
        return {'FINISHED'}
  • bl_idname - A unique identifier string in the form "module_name.operator_name", all lowercase and with a dot. This is how you call the operator from code or UI (bpy.ops.simple_addon.say_hello()).
  • bl_label - User-facing label that appears on buttons/menus.
  • bl_description - Tooltip/description shown in the UI.
  • execute(self, context) - Core method called when the operator runs (synchronous execution). context gives access to Blender's current state (active object, scene, area, etc.). self.report({'INFO'}, "…") shows a small message in Blender's info bar / status (good for user feedback). print("…") prints to the system/Blender console (useful for debugging). Returns a set like {'FINISHED'} or {'CANCELLED'}. Blender uses this result to know whether the operator completed successfully.

3) Panel class - UI placement

We can then get to the Panel subclass to add UI in Blender:

class SIMPLEADDON_PT_panel(bpy.types.Panel):
    bl_label = "Simple Addon Panel"
    bl_idname = "SIMPLEADDON_PT_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Simple'

    def draw(self, context):
        layout = self.layout
        layout.operator("simple_addon.say_hello")
  • bl_label - panel title shown in the UI.
  • bl_idname - unique panel identifier.
  • bl_space_type = 'VIEW_3D' tells Blender this panel belongs in the 3D Viewport area.
  • bl_region_type = 'UI' places it in the right-side region (the N-panel). Other regions exist (e.g., 'TOOLS', 'WINDOW').
  • bl_category = 'Simple' - The tab name in the sidebar. The panel will appear under a tab labeled “Simple”.
  • draw(self, context) is called to draw UI layout.
  • self.layout is a UILayout object used to place buttons, labels, properties, etc.
  • layout.operator("simple_addon.say_hello") creates a button that, when clicked, calls the operator with bl_idname "simple_addon.say_hello". The button text is taken from the operator's bl_label.

4) Register / unregister functions

Blender requires classes that define UI, operators, panels, properties, etc., to be registered so Blender knows about them:

def register():
    bpy.utils.register_class(SIMPLEADDON_OT_hello)
    bpy.utils.register_class(SIMPLEADDON_PT_panel)

def unregister():
    bpy.utils.unregister_class(SIMPLEADDON_PT_panel)
    bpy.utils.unregister_class(SIMPLEADDON_OT_hello)
  • bpy.utils.register_class(Class) registers a class; unregister_class removes it.
  • It's important to unregister classes in the reverse order of registration, especially when classes reference each other. This is why the panel is unregistered before the operator.
  • When the addon is enabled in Preferences, Blender calls register(). When disabled, it calls unregister().

We put the full code in a Python file addon.py:

bl_info = {
    "name": "Simple Addon Example",
    "author": "Your Name",
    "version": (1, 0),
    "blender": (4, 0, 0),
    "location": "View3D > Sidebar > Simple Tab",
    "description": "A simple example addon that prints a message",
    "category": "3D View",
}

import bpy

class SIMPLEADDON_OT_hello(bpy.types.Operator):
    bl_idname = "simple_addon.say_hello"
    bl_label = "Say Hello"
    bl_description = "Prints a message to the console"

    def execute(self, context):
        self.report({'INFO'}, "Hello from Blender Addon!")
        print("Hello from Blender Addon!")
        return {'FINISHED'}

class SIMPLEADDON_PT_panel(bpy.types.Panel):
    bl_label = "Simple Addon Panel"
    bl_idname = "SIMPLEADDON_PT_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Simple'

    def draw(self, context):
        layout = self.layout
        layout.operator("simple_addon.say_hello")

def register():
    bpy.utils.register_class(SIMPLEADDON_OT_hello)
    bpy.utils.register_class(SIMPLEADDON_PT_panel)

def unregister():
    bpy.utils.unregister_class(SIMPLEADDON_PT_panel)
    bpy.utils.unregister_class(SIMPLEADDON_OT_hello)

4. Running and Packaging Your Add-on

Once you’ve written your add-on script, you can load it into Blender and test it right away. No tools required.

  1. Save your script - Save your Python file with a clear name like my_addon.py.
  2. Open Blender’s Add-ons Preferences - Go to Edit → Preferences → Add-ons. This is where Blender manages all installed extensions.
  3. Install the add-on - Click the Install… button at the top of the preferences window. Select your my_addon.py file and click Install Add-on.
  4. Enable it - After installing, your add-on should appear in the list. Find it (you can search for “My Add-on”) and check the box to enable it if it's not already.
  1. Check it in the interface - Open the 3D Viewport, open the sidebar, and look for the tab named Simple. Your custom panel should be there, ready to use!

When you want to share your add-on with others, you can upload it to GitHub, Blender Artists, or Gumroad for distribution. Add a short README.md explaining what the add-on does and how to install it.

For add-ons with multiple files (e.g. separate modules, icons, or resources), create a folder then zip the entire folder (my_addon.zip) and share that. Blender can install .zip archives directly via the same Install… button so no need to extract it beforehand. The main entry point must be named __init__.py, since Blender treats it as a Python package.


Conclusion

Creating UI for Blender add-ons is intimidating at first, but it’s one of the easiest ways to share a tool you created. Once you understand how panels and layouts work, you can quickly add buttons, properties, and organized sections that users will find intuitive.

Have a look at the code repository on Github to try the example yourself.

Start small by adding a simple panel, a label, and a button to create an action, and build from there!

📽️
To learn more about the animation process consider joining our Discord community! We connect with over a thousand experts who share best practices and occasionally organize in-person events. We’d be happy to welcome you! 😊

Spread the word

Keep reading