TMHSDigital

bl-info-migration

Migrate a legacy bl_info-format add-on to the Extensions Platform. Three concrete steps, before-and-after diff, dual-format pattern for backward compatibility, and answers to "is bl_info still supported?" Targets Blender 5.2 LTS.

TMHSDigital 17 3 Actualitzat fa 1 mes
GitHub

Instal·la

npx skillscat add tmhsdigital/blender-developer-tools/bl-info-migration

Instal·la des del registre de SkillsCat.

SKILL.md

bl_info Migration to the Extensions Platform

Trigger

Use this skill when the user:

  • Has an existing add-on with a bl_info = {...} dictionary at the top of __init__.py
  • Wants to publish to extensions.blender.org or distribute as a .zip for the Extensions Platform
  • Asks "is bl_info dead?" or "do I have to migrate?"
  • Sees a deprecation warning at install time about legacy add-ons
  • Mentions blender_manifest.toml, "Install legacy Add-on", or "the new extension system"

Is bl_info still supported?

Yes, with caveats.

  • In 5.1 stable: bl_info is supported as a fallback for legacy add-ons. The Edit > Preferences > Add-ons panel still has "Install legacy Add-on" and recognizes bl_info dicts.
  • For the Extensions Platform (extensions.blender.org and the new add-ons UI): you need a blender_manifest.toml. bl_info is ignored.
  • Dual format is officially recognized: ship a blender_manifest.toml and keep the bl_info dict. The platform reads the manifest; legacy installers read bl_info. Both code paths work without conditional logic.

The Blender Foundation's stated direction is that the Extensions Platform is the future and bl_info-only add-ons will degrade in discoverability over time, but they are not announcing a removal date.

The three migration steps

A migration from a bl_info-only add-on to the Extensions Platform takes three small mechanical changes.

Step 1: Generate blender_manifest.toml

Create a new file blender_manifest.toml next to __init__.py. Translate the bl_info fields:

bl_info key Manifest key Notes
"name" name = "..." Same string
"version": (1, 2, 3) version = "1.2.3" Tuple becomes a string
"blender": (4, 5, 0) blender_version_min = "4.5.0" Renamed; semantics same
"author" maintainer = "Author <email>" Email recommended
"description" tagline = "..." Renamed
"category" tags = ["..."] Now a list of allowed tags
"location", "warning" (no equivalent) Drop
(none) id = "..." New required field; reverse-DNS or short slug
(none) schema_version = "1.0.0" Required
(none) type = "add-on" Required
(none) license = ["SPDX:..."] Required SPDX expression

Minimal manifest:

schema_version = "1.0.0"

id = "my_addon"
version = "1.2.3"
name = "My Addon"
tagline = "What it does in one sentence"
maintainer = "Author Name <author@example.com>"
type = "add-on"

blender_version_min = "4.5.0"

license = [
    "SPDX:GPL-3.0-or-later",
]

tags = ["3D View", "Mesh"]

The full schema and allowed tag list live in the Blender extensions documentation.

Step 2: Delete bl_info (or keep both)

If you only want to support 5.x and the Extensions Platform: delete the bl_info = {...} block from __init__.py.

If you want to keep working with the 4.x "Install legacy Add-on" path while also publishing to the platform: leave bl_info in place. This is the dual-format pattern. The two metadata sources do not need to agree perfectly; the platform reads only the manifest, the legacy installer reads only bl_info.

Step 3: Convert imports to relative or __package__

Legacy add-ons commonly use absolute imports:

from my_addon import operators
from my_addon.ui import panels

These break under the Extensions Platform because the package name is no longer my_addon at runtime; it's a generated synthetic name like bl_ext.user_default.my_addon. Two fixes that work everywhere:

Relative imports:

from . import operators
from .ui import panels

__package__ lookup (when you need the full package name as a string):

import importlib

operators = importlib.import_module(f"{__package__}.operators")

Both forms are stable across the legacy and Extensions Platform load paths.

Worked example: before and after

Before (legacy, single file __init__.py)

bl_info = {
    "name": "Cube Tools",
    "author": "Acme <acme@example.com>",
    "version": (1, 0, 0),
    "blender": (4, 5, 0),
    "location": "View3D > Sidebar > Cube",
    "description": "Quick cube operations",
    "category": "Object",
}

import bpy
from cube_tools import operators
from cube_tools.ui import panels


def register():
    operators.register()
    panels.register()


def unregister():
    panels.unregister()
    operators.unregister()

After (Extensions Platform)

blender_manifest.toml:

schema_version = "1.0.0"

id = "cube_tools"
version = "1.0.0"
name = "Cube Tools"
tagline = "Quick cube operations"
maintainer = "Acme <acme@example.com>"
type = "add-on"

blender_version_min = "4.5.0"

license = [
    "SPDX:GPL-3.0-or-later",
]

tags = ["Object"]

__init__.py:

import bpy

from . import operators
from .ui import panels


def register():
    operators.register()
    panels.register()


def unregister():
    panels.unregister()
    operators.unregister()

The bl_info dict is gone; the imports are now relative; everything else is identical.

After (dual format, supports both load paths)

Same blender_manifest.toml as above, plus keeping a slimmed bl_info for 4.x legacy users:

bl_info = {
    "name": "Cube Tools",
    "author": "Acme",
    "version": (1, 0, 0),
    "blender": (4, 5, 0),
    "category": "Object",
}

import bpy
from . import operators
from .ui import panels

Note: the imports are still relative. Relative imports work in both load paths; absolute imports only work in the legacy path.

Packaging for distribution

Once the manifest exists, build the Extension .zip:

blender --command extension build --source-dir .\cube_tools --output-dir .\dist

This produces a .zip in dist\ that the user installs via Edit > Preferences > Add-ons > Install... or that you upload to extensions.blender.org.

Common AI mistakes

  • Forgetting id in the manifest. id is required and must be a stable slug; changing it later is a breaking change that disconnects users from updates.
  • Using a tuple for version in the manifest. bl_info uses tuples, the manifest uses a string. Dot-separated semver, e.g., "1.2.3".
  • Keeping absolute imports during dual-format migration. They break at runtime under the Extensions Platform load path. Convert to relative.
  • Skipping SPDX in license. The platform rejects manifests without a license expression. ["SPDX:GPL-3.0-or-later"] matches typical Blender add-on practice; commercial add-ons use other SPDX strings.
  • Misnaming the file. It must be exactly blender_manifest.toml, lowercase, in the package root.
  • Mixing permissions defaults. The manifest has a permissions key for network, file IO, etc. Default to declaring nothing if you don't need elevated permissions; over-declaration triggers user warnings.

Version correctness

Topic 4.5 LTS 5.1 stable
bl_info recognized Yes (primary path) Yes (legacy fallback only)
blender_manifest.toml recognized Yes (Extensions Platform was added in 4.2) Yes
Synthetic package name bl_ext.<repo>.<id> Same
extension build command Available Available
Schema version 1.0.0 1.0.0

The Extensions Platform shipped in Blender 4.2. Migrations done now will work on 4.2+ and 5.x without further changes. Code targeting 4.0 or earlier still needs the bl_info-only path.

See also

  • Skill addon-scaffolding: scaffolding a new Extensions Platform add-on from scratch.
  • Rule target-extensions-platform-format: encodes the policy that new add-ons should be Extensions-first.
  • Template extension-addon-template: a working starter to compare your migration against.

References