main.js with module.exports.metadata and init(), using the global api. Those still load, marked Legacy on the Expansions page, but they are deprecated and the SDK is where new work goes.
The shape
- Move
metadataintometa.json. AddsdkVersion(^2.0.0) andpermissions(see below). Addconfig.json. - Replace
init()with top-level code in the start file, or a class if you prefer. - Replace path strings with imports:
__dirname + '/pages/X.jsx'becomesimport X from './pages/X'. - In components, replace the props
api,useComponents()anduseProjectStatewith imports fromsdkandsdk/ui. - Delete
api.expansion.call(...): logic and UI are one module graph now, so just import the function.
Point by point
Permissions
Legacy expansions had none:api could reach the whole project and the filesystem. In the SDK, declare what you use:
- reading items or project files:
project:read - writing them:
project:write fetch:network- clipboard:
clipboard
Not available yet
These have no SDK point, so an expansion that depends on them stays legacy for now:- Export formats:
postExportFormat,registerExportFormat,extendExistingFormat,registerExporter - Server reload commands:
postReloadCommand postTemplate,postBlueprintCategory,postBlueprintPiecepostAssets,postEditorModuleOverrides,postItemTypeStates,postCreatorTypeCompatibilities,postSettingsElementTypeexecBlockbench, andloadFileon paths outside the expansionregisterBackgroundModule; background scripts (sdk.run) are planned
Things that change quietly
- Ids are namespaced. A page registered as
counterismy_expansion.counter, and its route changes to match. Anything you hard-coded needs updating. - Imports are real. Helpers, shared files and CSS work now, which is usually a chance to delete the workarounds the old modulars needed.
- Cleanup is automatic. Anything you registered is removed on reload or disable; remove hand-rolled teardown.
- No Node. Any
require('fs')or child process work has no equivalent and must go through the SDK, or stay legacy.