Skip to content
Back to skills

Addon Scaffolding

ASecurity

Scaffold a Blender add-on as an Extensions Platform package: blender_manifest.toml, modular file layout, register_classes_factory, symmetric register/unregister. Use when the user starts a new add-on or extension, writes or fixes blender_manifest.toml, asks which layout or registration pattern to use on Blender 4.5 LTS or 5.x, or has classes that fail to register or unregister cleanly. Targets 5.2 LTS with 4.5 LTS fallback.

  • 16 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 23, 2026
developmentpythongonodegitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 5, 2026

npx -y skills add TMHSDigital/Blender-Developer-Tools --skill addon-scaffolding --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Addon Scaffolding?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Addon Scaffolding
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tmhsdigital-addon-scaffolding/badge)](https://www.skillsdirectory.com/skills/tmhsdigital-addon-scaffolding)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: addon-scaffolding
description: "Scaffold a Blender add-on as an Extensions Platform package: blender_manifest.toml, modular file layout, register_classes_factory, symmetric register/unregister. Use when the user starts a new add-on or extension, writes or fixes blender_manifest.toml, asks which layout or registration pattern to use on Blender 4.5 LTS or 5.x, or has classes that fail to register or unregister cleanly. Targets 5.2 LTS with 4.5 LTS fallback."
standards-version: 1.10.0
---

# Addon Scaffolding (Extensions Platform)

## Trigger

Use this skill when the user:

- Wants to start a new Blender add-on
- Mentions `bl_info`, `blender_manifest.toml`, "extension", "register_classes_factory"
- Is migrating an existing add-on from the legacy `bl_info` format to the Extensions Platform
- Is unsure which file layout or registration pattern to use for Blender 4.5+ or 5.x

## Required inputs

- **Add-on name and short id** (snake_case for the manifest `id`, human-readable `name`)
- **Target Blender versions** (defaults to `blender_version_min = "4.5.0"` so the add-on works on 4.5 LTS, 5.1 and 5.2 LTS)
- **Maintainer string** (`Name <email@example.com>` form)
- **License SPDX identifier** (`SPDX:MIT`, `SPDX:GPL-2.0-or-later`, etc.)

## The Extensions Platform format (5.x default)

Since 4.2 and required in 5.x for new submissions to the official extensions site, add-ons ship as Extensions. An Extension is a directory containing a `blender_manifest.toml` plus Python modules.

### `blender_manifest.toml` required fields

```toml
schema_version = "1.0.0"

id = "my_addon"
version = "0.1.0"
name = "My Add-on"
tagline = "Short description, max 64 chars, no trailing period"
maintainer = "Your Name <you@your-domain.example>"
type = "add-on"

blender_version_min = "4.5.0"

license = ["SPDX:GPL-3.0-or-later"]
copyright = ["2026 TMHSDigital"]
```

Field rules that AIs commonly get wrong:

- `tagline` is hard-capped at 64 characters and **must not end with a period**. Blender's manifest validator rejects both.
- `id` must be a valid Python identifier with no double underscore and no leading or trailing underscore: `my_tool` passes; `com.acme.tool`, `_tool`, `tool_` and `my__tool` fail `blender --command extension validate` (`pkg_idname_is_valid_or_error` in the bundled `blender_ext.py`, same rules on 4.5 and 5.2). The directory name does not need to match the `id` but conventionally does.
- `license` and `copyright` are **arrays of strings**, not bare strings.
- `blender_version_min` is the floor. Setting it to `"4.5.0"` lets the same code run on 4.5 LTS and any 5.x. Setting it to `"5.0.0"` cuts off LTS users with no warning.
- `schema_version` is `"1.0.0"` for the current manifest schema. Do not bump it.

Optional but commonly wanted:

```toml
website = "https://github.com/TMHSDigital/your-repo"
tags = ["Mesh", "Geometry Nodes"]
permissions = { network = "Optional, only used to fetch updates" }
```

`tags` must come from Blender's known tag list. `permissions` maps each capability the add-on uses to a human-readable reason; the valid keys are `files`, `network`, `clipboard`, `camera` and `microphone`. It is a **declaration** shown to users and reviewers on the extensions platform, not a sandbox: Blender does not restrict what add-on Python can do, with or without it. Declare everything the code actually uses. For network access, also honor `bpy.app.online_access` (the user's "Allow Online Access" preference) and do nothing online when it is `False`.

## File layout

The conventional layout for a non-trivial add-on:

```
my_addon/
  blender_manifest.toml
  __init__.py            # registration entry point only
  operators.py           # Operator subclasses
  panels.py              # Panel subclasses
  properties.py          # PropertyGroup subclasses, PointerProperty bindings
  utils.py               # Pure helpers, no bpy imports beyond types
```

Keep `__init__.py` small. It registers and unregisters classes; everything else lives in submodules.

## The `register_classes_factory` pattern

This is the canonical 5.x registration pattern. It eliminates the loop boilerplate that AIs frequently bug.

```python
import bpy
from bpy.utils import register_classes_factory

from . import operators, panels, properties

classes = (
    properties.MyAddonProperties,
    operators.MESH_OT_my_addon_action,
    panels.VIEW3D_PT_my_addon,
)

_register_classes, _unregister_classes = register_classes_factory(classes)


def register():
    _register_classes()
    bpy.types.Scene.my_addon = bpy.props.PointerProperty(type=properties.MyAddonProperties)


def unregister():
    del bpy.types.Scene.my_addon
    _unregister_classes()
```

Why this pattern:

- `register_classes_factory` returns a `(register, unregister)` pair that loops over `classes` in the right direction (forward for register, reverse for unregister) and skips the wrong-order foot-gun.
- Properties bound to types (`bpy.types.Scene.my_addon = ...`) **must be deleted before** the corresponding `PropertyGroup` class is unregistered. Hence `del bpy.types.Scene.my_addon` precedes `_unregister_classes()`.
- Class registration order matters: `PropertyGroup` subclasses must come before any class that references them via `PointerProperty`.

## Working example: minimal __init__.py

```python
import bpy
from bpy.utils import register_classes_factory


class MY_ADDON_PG_settings(bpy.types.PropertyGroup):
    intensity: bpy.props.FloatProperty(name="Intensity", default=1.0, min=0.0, max=10.0)


class MESH_OT_my_addon_action(bpy.types.Operator):
    bl_idname = "mesh.my_addon_action"
    bl_label = "My Addon Action"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.active_object is not None and context.active_object.type == 'MESH'

    def execute(self, context):
        obj = context.active_object
        settings = context.scene.my_addon
        self.report({'INFO'}, f"Ran on {obj.name} with intensity {settings.intensity:.2f}")
        return {'FINISHED'}


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

    def draw(self, context):
        layout = self.layout
        settings = context.scene.my_addon
        layout.prop(settings, "intensity")
        layout.operator("mesh.my_addon_action")


classes = (
    MY_ADDON_PG_settings,
    MESH_OT_my_addon_action,
    VIEW3D_PT_my_addon,
)

_register_classes, _unregister_classes = register_classes_factory(classes)


def register():
    _register_classes()
    bpy.types.Scene.my_addon = bpy.props.PointerProperty(type=MY_ADDON_PG_settings)


def unregister():
    del bpy.types.Scene.my_addon
    _unregister_classes()
```

## Common AI mistakes

1. **Emitting only `bl_info` and skipping `blender_manifest.toml`**. New add-ons must ship as Extensions for the official extensions platform. `bl_info` may appear alongside the manifest as a fallback for backward compatibility, but the manifest is the source of truth.

2. **Manual register/unregister loops with the wrong direction**:

   ```python
   # WRONG: same direction in unregister, classes referencing each other will fail
   for cls in classes:
       bpy.utils.register_class(cls)

   def unregister():
       for cls in classes:
           bpy.utils.unregister_class(cls)  # missing reversed()
   ```

   Use `register_classes_factory` and let it own the order.

3. **Forgetting to delete bound properties before unregister**. Trying to unregister `MY_ADDON_PG_settings` while `bpy.types.Scene.my_addon` still points to it will hard-fail on Blender restart and leak.

4. **Overlong tagline or trailing period in tagline**. The validator rejects both.

5. **Hardcoding `blender_version_min = "5.0.0"` without reason**. Cuts off the LTS user base. Default to `"4.5.0"` unless you genuinely require a 5.x-only API.

## Compatibility paths

`blender_manifest.toml` alone covers 4.2 and later, including 4.5 LTS and 5.2 LTS: with `blender_version_min = "4.5.0"`, a `bl_info` dict adds nothing. Keep `bl_info` alongside the manifest only if the add-on must also install on 4.1 or earlier, or through the legacy add-on folder / "Install legacy Add-on" path. Blender prefers the manifest when both are present.

```python
bl_info = {
    "name": "My Addon",
    "blender": (4, 5, 0),
    "version": (0, 1, 0),
    "category": "Mesh",
}
```

## Related

- See the `extension-addon-template` template under [`templates/`](https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/templates) for a ready-to-copy version of this layout.
- See the `operators`, `ui-panels`, and `custom-properties` skills for the contents of each submodule.
- See rules `target-extensions-platform-format` and `type-annotate-props-and-defend-context`.

<!-- examples:begin (generated by scripts/build_examples_index.py from examples/skills.json; do not edit) -->
## Runnable examples

No runnable example proves this skill yet (tracked in [#378](https://github.com/TMHSDigital/Blender-Developer-Tools/issues/378)).

<!-- examples:end -->

## References

- Extensions Platform manual: https://docs.blender.org/manual/en/latest/advanced/extensions/index.html
- `bpy.utils.register_classes_factory`: https://docs.blender.org/api/current/bpy.utils.html
- Manifest reference: https://docs.blender.org/manual/en/latest/advanced/extensions/getting_started.html

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.