Skip to content
Back to skills

Blender Syntax Addons

ASecurity

Use when creating a Blender addon or extension, or migrating from legacy bl_info to blender_manifest.toml (required in 5.0). Prevents the common mistake of using bl_info in Blender 5.0+ where only the manifest format is supported. Covers register/unregister lifecycle, multi-file addon structure, AddonPreferences, class naming, and extension packaging for extensions.blender.org. Keywords: addon, extension, bl_info, blender_manifest.toml, register, unregister, AddonPreferences, packaging, exten...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
ai-agentspythongobashnodeexpresstestingapi

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add sheepk95/The-Cartographer --skill blender-syntax-addons --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Blender Syntax Addons?

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

Security grade badge for Blender Syntax Addons
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sheepk95-blender-syntax-addons/badge)](https://www.skillsdirectory.com/skills/sheepk95-blender-syntax-addons)

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: blender-syntax-addons
description: >
  Use when creating a Blender addon or extension, or migrating from legacy bl_info to
  blender_manifest.toml (required in 5.0). Prevents the common mistake of using bl_info
  in Blender 5.0+ where only the manifest format is supported. Covers register/unregister
  lifecycle, multi-file addon structure, AddonPreferences, class naming, and extension
  packaging for extensions.blender.org.
  Keywords: addon, extension, bl_info, blender_manifest.toml, register, unregister, AddonPreferences, packaging, extensions.blender.org, addon boilerplate, how to register addon, addon preferences panel.
license: MIT
compatibility: "Designed for Claude Code. Requires Blender 3.x/4.x/5.x with Python."
metadata:
  author: OpenAEC-Foundation
  version: "1.0"
---

# blender-syntax-addons

## Quick Reference

### Version Decision Tree

```
Creating a Blender addon?
│
├── Target Blender ≥ 4.2?
│   ├── YES → Use blender_manifest.toml (extension system)
│   │         NO bl_info dict. NO __init__.py bl_info.
│   │         See: §Extension System
│   │
│   └── Also support < 4.2?
│       └── YES → Provide BOTH bl_info AND blender_manifest.toml
│                 Blender picks the correct one automatically
│
└── Target Blender < 4.2 only?
    └── Use bl_info dict in __init__.py
        See: §Legacy bl_info
```

### Critical Warnings

**NEVER** omit `register()` and `unregister()` functions — Blender REQUIRES both in every addon.

**NEVER** set `bl_idname` on `AddonPreferences` to a hardcoded string — ALWAYS use `__package__`.

**NEVER** register classes in wrong order — PropertyGroups and dependencies MUST register before classes that reference them.

**NEVER** write to the addon's own installation directory — use `bpy.utils.extension_path_user(__package__, create=True)` for writable storage (4.2+).

**NEVER** install pip packages at runtime — bundle wheels in `wheels/` directory instead (4.2+).

**NEVER** use `from my_addon import X` at module level in sub-modules — use `from . import X` (relative imports) for multi-file addons.

**ALWAYS** use the class naming convention `{ADDON}_{TYPE}_{name}` — Blender enforces this pattern for all registered types.

**ALWAYS** unregister classes in reverse order of registration.

---

## Essential Patterns

### Pattern 1: Legacy bl_info Dict (Blender < 4.2)

The `bl_info` dict MUST be at module-level in `__init__.py`. Blender parses it statically (no Python execution), so it MUST NOT use variables or expressions.

```python
# Blender 3.x/4.0/4.1: REQUIRED in __init__.py (or single-file addon)
bl_info = {
    "name": "My Addon",
    "author": "Developer Name",
    "version": (1, 0, 0),
    "blender": (3, 6, 0),          # Minimum Blender version
    "location": "View3D > Sidebar > My Tab",
    "description": "Short description of addon purpose",
    "warning": "",                  # Optional: shown as warning in prefs
    "doc_url": "https://example.com/docs",
    "tracker_url": "https://example.com/issues",
    "category": "Object",          # Must be a valid Blender category
}
```

Valid `category` values: `3D View`, `Add Mesh`, `Add Curve`, `Animation`, `Compositing`, `Development`, `Game Engine`, `Import-Export`, `Lighting`, `Material`, `Mesh`, `Node`, `Object`, `Paint`, `Physics`, `Render`, `Rigging`, `Scene`, `Sequencer`, `System`, `Text Editor`, `UV`.

### Pattern 2: Extension Manifest (Blender 4.2+)

Extensions use `blender_manifest.toml` instead of `bl_info`. The `__init__.py` contains ONLY `register()`/`unregister()` — no `bl_info` dict.

```toml
# blender_manifest.toml: REQUIRED for extensions (Blender 4.2+)
schema_version = "1.0.0"

id = "my_addon"
version = "1.0.0"
name = "My Addon"
tagline = "Short description without ending punctuation"
maintainer = "Developer Name <dev@example.com>"
type = "add-on"

# Minimum Blender version (must be >= 4.2.0 for extensions)
blender_version_min = "4.2.0"
# blender_version_max = "5.1.0"  # Optional: first UNSUPPORTED version

license = ["SPDX:GPL-3.0-or-later"]
# copyright = ["2024 Developer Name"]  # Optional

# Optional: category tags (from Blender's fixed tag list)
tags = ["Object", "Scene"]

# Optional: bundled Python wheels
# wheels = ["./wheels/some_lib-1.0.0-py3-none-any.whl"]

# Optional: platform restrictions
# platforms = ["windows-x64", "macos-arm64", "linux-x64"]

# Optional: permission declarations (required for network/file access)
# [permissions]
# network = "Downloads data from remote server"
# files = "Reads reference files from disk"
# clipboard = "Copies results to clipboard"
```

### Pattern 3: Register / Unregister Lifecycle

```python
# Blender 3.x/4.x/5.x: REQUIRED functions
import bpy

classes = (
    MyPropertyGroup,     # PropertyGroups FIRST
    MY_OT_operator,      # Then operators
    MY_PT_panel,         # Then UI elements
    MyAddonPreferences,  # Preferences can go anywhere
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    # Attach properties AFTER registering their types
    bpy.types.Scene.my_props = bpy.props.PointerProperty(type=MyPropertyGroup)

def unregister():
    # Remove properties BEFORE unregistering their types
    del bpy.types.Scene.my_props
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
```

Registration order rules:
1. `PropertyGroup` classes BEFORE any class that references them via `PointerProperty`
2. Parent panels BEFORE sub-panels (if using `bl_parent_id`)
3. `unregister()` MUST reverse the order
4. Property attachments (`bpy.types.Scene.my_prop = ...`) AFTER `register_class()`
5. Property deletions (`del bpy.types.Scene.my_prop`) BEFORE `unregister_class()`

### Pattern 4: Class Naming Convention (Enforced)

Blender enforces: `{ADDON}_{TYPE}_{name}` where `{ADDON}` is uppercase addon identifier, `{TYPE}` is a two-letter type code, and `{name}` is lowercase with underscores.

| Type Code | Base Class | Example |
|-----------|-----------|---------|
| `OT` | `bpy.types.Operator` | `MYADDON_OT_do_something` |
| `PT` | `bpy.types.Panel` | `MYADDON_PT_main_panel` |
| `MT` | `bpy.types.Menu` | `MYADDON_MT_context_menu` |
| `UL` | `bpy.types.UIList` | `MYADDON_UL_item_list` |
| `HT` | `bpy.types.Header` | `MYADDON_HT_header` |
| `KI` | `bpy.types.KeyingSetInfo` | `MYADDON_KI_keying` |

For `bl_idname` on operators: use `"addonname.action_name"` (all lowercase, dot-separated). Example: `bl_idname = "myaddon.export_data"`.

### Pattern 5: Multi-File Addon Structure

```
my_addon/
├── __init__.py          # bl_info (or none for 4.2+) + register/unregister
├── blender_manifest.toml  # 4.2+ only
├── operators.py         # Operator classes
├── panels.py            # Panel classes
├── properties.py        # PropertyGroup classes
├── preferences.py       # AddonPreferences class
├── utils.py             # Helper functions (no bpy.types subclasses)
└── wheels/              # Bundled pip packages (4.2+ only)
    └── some_lib.whl
```

**`__init__.py`** with reload support (legacy addon):

```python
bl_info = {
    "name": "My Addon",
    "blender": (3, 6, 0),
    "category": "Object",
    "version": (1, 0, 0),
}

# Reload support: REQUIRED for F8 / addon toggle without restart
if "bpy" in locals():
    import importlib
    importlib.reload(operators)
    importlib.reload(panels)
    importlib.reload(properties)
else:
    from . import operators
    from . import panels
    from . import properties

import bpy

classes = (
    properties.MyPropertyGroup,
    operators.MYADDON_OT_example,
    panels.MYADDON_PT_main,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.my_props = bpy.props.PointerProperty(
        type=properties.MyPropertyGroup
    )

def unregister():
    del bpy.types.Scene.my_props
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
```

**`__init__.py`** for extension (4.2+) — NO bl_info, NO reload boilerplate:

```python
from . import operators, panels, properties

import bpy

classes = (
    properties.MyPropertyGroup,
    operators.MYADDON_OT_example,
    panels.MYADDON_PT_main,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.my_props = bpy.props.PointerProperty(
        type=properties.MyPropertyGroup
    )

def unregister():
    del bpy.types.Scene.my_props
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
```

### Pattern 6: AddonPreferences

```python
# Blender 3.x/4.x/5.x
class MyAddonPreferences(bpy.types.AddonPreferences):
    bl_idname = __package__  # MUST match addon module name — NEVER hardcode

    api_key: bpy.props.StringProperty(
        name="API Key",
        subtype='PASSWORD',
    )
    debug_mode: bpy.props.BoolProperty(
        name="Debug Mode",
        default=False,
    )
    output_path: bpy.props.StringProperty(
        name="Output Path",
        subtype='DIR_PATH',
    )

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "api_key")
        layout.prop(self, "debug_mode")
        layout.prop(self, "output_path")
```

Access preferences at runtime:

```python
# Blender 3.x/4.x/5.x
prefs = bpy.context.preferences.addons[__package__].preferences
value = prefs.debug_mode
```

### Pattern 7: Extension Packaging (4.2+)

Build and validate an extension for distribution:

```bash
# Validate manifest
blender --command extension validate blender_manifest.toml

# Build .zip for upload to extensions.blender.org
blender --command extension build --source-dir ./my_addon --output-filepath my_addon.zip

# Install locally for testing
blender --command extension install-file my_addon.zip
```

Extension guidelines for extensions.blender.org:
- MUST check `bpy.app.online_access` before any network request
- MUST NOT install pip packages at runtime — bundle in `wheels/`
- MUST NOT modify other addons or extensions
- MUST NOT write to the addon installation directory
- MUST use relative imports only — no `sys.path` manipulation
- MUST load all modules as sub-modules within package namespace
- SHOULD follow PEP 8

### Pattern 8: User-Writable Storage (4.2+)

```python
# Blender 4.2+: get a writable directory for addon data
import os
user_dir = bpy.utils.extension_path_user(__package__, create=True)
config_path = os.path.join(user_dir, "config.json")
```

---

## Common Operations

### Detect Addon vs Extension Context

```python
# Blender 3.x/4.x/5.x: check if running as extension
import os

def is_extension():
    """Check if this addon is installed as a Blender 4.2+ extension."""
    manifest = os.path.join(os.path.dirname(__file__), "blender_manifest.toml")
    return os.path.exists(manifest)
```

### Version-Conditional Registration

```python
# Support both legacy and extension systems
def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    # Handler registration
    bpy.app.handlers.load_post.append(on_load)

def unregister():
    bpy.app.handlers.load_post.remove(on_load)
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

# For both: if __name__ == "__main__" block for script testing
if __name__ == "__main__":
    register()
```

### Keymap Registration in Addon

```python
addon_keymaps = []

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    # Add keymap entry
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon
    if kc:
        km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
        kmi = km.keymap_items.new("myaddon.do_something", 'D', 'PRESS', ctrl=True)
        addon_keymaps.append((km, kmi))

def unregister():
    # Remove keymaps BEFORE unregistering classes
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
```

### Migration: bl_info to blender_manifest.toml

| bl_info field | Manifest equivalent | Notes |
|---------------|-------------------|-------|
| `"name"` | `name` | Same |
| `"author"` | `maintainer` | Add email: `"Name <email>"` |
| `"version"` | `version` | Tuple → string: `(1,0,0)` → `"1.0.0"` |
| `"blender"` | `blender_version_min` | Tuple → string, min `"4.2.0"` |
| `"description"` | `tagline` | No ending punctuation |
| `"category"` | `tags` | String → array: `"Object"` → `["Object"]` |
| `"doc_url"` | `website` | Same |
| `"warning"` | *(removed)* | No equivalent in manifest |
| `"tracker_url"` | *(removed)* | No equivalent in manifest |
| *(new)* | `id` | Lowercase identifier, no spaces |
| *(new)* | `schema_version` | Always `"1.0.0"` |
| *(new)* | `license` | SPDX format: `["SPDX:GPL-3.0-or-later"]` |
| *(new)* | `type` | `"add-on"` or `"theme"` |
| *(new)* | `maintainer` | `"Name <email>"` format |

---

## Reference Links

- [references/methods.md](references/methods.md) — Complete API signatures for addon registration, preferences, and extension utilities
- [references/examples.md](references/examples.md) — Working code examples for single-file, multi-file, and extension addons
- [references/anti-patterns.md](references/anti-patterns.md) — What NOT to do when building Blender addons

### Official Sources

- https://docs.blender.org/api/current/info_overview.html
- https://docs.blender.org/manual/en/latest/advanced/scripting/addon_tutorial.html
- https://developer.blender.org/docs/handbook/extensions/
- https://developer.blender.org/docs/handbook/extensions/addon_guidelines/
- https://developer.blender.org/docs/features/extensions/schema/1.0.0/

Files in this skill

  • LICENSE1 KB
  • SKILL.md13.4 KB
  • references/anti-patterns.md8.4 KB
  • references/examples.md14.7 KB
  • references/methods.md8.7 KB

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.