diff --git a/.github/scripts/publish-bonsai-releases.py b/.github/scripts/publish-bonsai-releases.py
new file mode 100755
index 0000000000..a393104e7c
--- /dev/null
+++ b/.github/scripts/publish-bonsai-releases.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env -S uv run
+# /// script
+# dependencies = [
+# "PyGithub",
+# "requests",
+# ]
+# ///
+
+import os
+from pathlib import Path
+
+import requests
+from github import Github
+from github.GitReleaseAsset import GitReleaseAsset
+
+EXTENSION_ID = "bonsai"
+CURRENT_PYTHON_VERSION = "py313"
+CURRENT_PLATFORMS = ["linux-x64", "macos-arm64", "windows-x64"]
+
+
+def publish_asset(asset: GitReleaseAsset, token: str, repo_root: Path) -> None:
+ """
+ Publish an asset to Blender Extensions.
+ Reference: https://extensions.blender.org/api/v1/swagger
+ """
+ temp_path = repo_root / asset.name
+
+ response = requests.get(asset.browser_download_url)
+ response.raise_for_status()
+ temp_path.write_bytes(response.content)
+
+ url = f"https://extensions.blender.org/api/v1/extensions/{EXTENSION_ID}/versions/upload/"
+ headers = {"Authorization": f"Bearer {token}"}
+
+ files = {"version_file": temp_path.read_bytes()}
+ response = requests.post(url, headers=headers, files=files)
+ response.raise_for_status()
+
+ temp_path.unlink()
+
+ print(f"✓ Published {asset.name}")
+
+
+def main() -> None:
+ token = os.getenv("BLENDER_EXTENSIONS_TOKEN")
+ if not token:
+ raise Exception("BLENDER_EXTENSIONS_TOKEN environment variable not set")
+
+ # Get the repository root
+ repo_root = Path(__file__).parent.parent.parent
+
+ # Read VERSION file
+ version_file = repo_root / "VERSION"
+ version = version_file.read_text().strip()
+
+ print(f"Current VERSION: {version}")
+
+ tag_name = f"bonsai-{version}"
+
+ # Get release from GitHub
+ gh = Github()
+ gh_repo = gh.get_repo("IfcOpenShell/IfcOpenShell")
+ release = gh_repo.get_release(tag_name)
+
+ assets = release.get_assets()
+
+ asset_platform_map: dict[str, tuple[GitReleaseAsset, str]] = {}
+ for asset in assets:
+ if CURRENT_PYTHON_VERSION not in asset.name:
+ continue
+ for platform in CURRENT_PLATFORMS:
+ if platform in asset.name:
+ asset_platform_map[asset.name] = (asset, platform)
+ break
+
+ if len(asset_platform_map) != len(CURRENT_PLATFORMS):
+ found_platforms = {platform for _, (_, platform) in asset_platform_map.items()}
+ missing_platforms = set(CURRENT_PLATFORMS) - found_platforms
+ raise Exception(
+ f"Expected {len(CURRENT_PLATFORMS)} assets but found {len(asset_platform_map)}. "
+ f"Missing: {', '.join(sorted(missing_platforms))}"
+ )
+
+ print("\nRelease assets:")
+ for asset_name in sorted(asset_platform_map.keys()):
+ print(f"- {asset_name}")
+
+ # https://extensions.blender.org/api/v1/swagger
+ print("\nPublishing assets to Blender Extensions:")
+ for asset_name, (asset, platform) in asset_platform_map.items():
+ publish_asset(asset, token, repo_root)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/publish-bonsai-releases.yml b/.github/workflows/publish-bonsai-releases.yml
new file mode 100644
index 0000000000..25d9a67d41
--- /dev/null
+++ b/.github/workflows/publish-bonsai-releases.yml
@@ -0,0 +1,16 @@
+name: Publish Bonsai Releases
+
+on:
+ workflow_dispatch:
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: astral-sh/setup-uv@v3
+
+ - run: uv run .github/scripts/publish-bonsai-releases.py
+ env:
+ BLENDER_EXTENSIONS_TOKEN: ${{ secrets.BLENDER_EXTENSIONS_TOKEN }}
diff --git a/src/bonsai/docs/guides/development/maintenance.rst b/src/bonsai/docs/guides/development/maintenance.rst
index 5550f7ba6c..f0d9796e12 100644
--- a/src/bonsai/docs/guides/development/maintenance.rst
+++ b/src/bonsai/docs/guides/development/maintenance.rst
@@ -96,7 +96,10 @@ Things to update:
- ``.github/workflows/ci-ifcsverchok.yml`` - release ifcsverchok Blender add-on in GitHub releases
- ``.github/workflows/ci-ifctester-pypi.yml`` - release `ifctester `_ to PyPI
- ``.github/workflows/ci-pyodide-wasm-release.yml`` - release pyodide wasm wheel to `wasm-wheels `_
-- Release Bonsai Blender extension - zip files from ci-bonsai.yml releases should be uploaded manually to `Blender extensions platform `_
+- ``.github/workflows/publish-bonsai-releases.yml`` - publish Bonsai Blender extension to `Blender extensions platform `_
+
+ - ❗ Requires ``BLENDER_EXTENSIONS_TOKEN`` secret to be set - ❗ not yet configured
+
- Publishing documentation and websites (see `website `_ repository):
- `ifcopenshell-docs.yml` - builds and publishes IfcOpenShell documentation to `docs.ifcopenshell.org `_ (`ifcopenshell_org_docs `_ repo)