Creating an addon
How to use sumeru-bp, place an addon on addons_path, generate imports, install and update it.
Overview
Addons live under sumeru_custom_addons/addons/. Scaffold with make new MODULE=… (or sumeru-bp), declare models with sdk.Model tags, list XML in the manifest, run make generate, then install.
Workspace. Work from sumeru_custom_addons (Go 1.26.2+, PostgreSQL). Default HTTP port is 8080.
Scaffold with sumeru-bp
Use the workspace tab for client work. Name flag aliases: -name, -bp, -boilerplate, -blueprint.
cd sumeru_custom_addonsmake new MODULE=my_modulemake install MODULES=my_module
cd sumerumake bp NAME=my_modulemake generate
Workspace make new runs sumeru-bp then make generate (writes zmodels.go / zrefs.go; custom init.go is regenerated to models-only — put hooks in models/). Use Core make only when scaffolding inside the sumeru repo.
Generated module
Browse the scaffold for my_module. Click a file in the tree or sidebar. Same layout as sumeru_custom_addons/addons/my_module.
addons/my_module/
├──
├──
├── models/
│ ├──
│ └── zmodels.go
├── views/
│ ├──
│ ├──
│ ├──
│ ├──
│ └──
└── security/
├──
└──
{ "name": "my_module", "display_name": "My Module", "version": "1.0.0", "author": "Your Company", "description": "Premium standard module for Sumeru ERP.", "depends": ["base"], "application": true, "data": [ "security/security.xml", "security/sys.access.csv", "views/actions.xml", "views/form_view.xml", "views/list_view.xml", "views/kanban_view.xml", "views/menus.xml" ]}
// Package my_module provides business logic and module initialization.package my_module import ( _ "sumeru_custom_addons/addons/my_module/models")
package models import ( "sumeru/core/sdk") // MyModule is the primary record for My Module.type MyModule struct { sdk.Model `sumeru:"model=my.module"` Name sdk.String `sumeru:"required,index,string=Name"` Description sdk.Text `sumeru:"string=Description"` Active sdk.Boolean `sumeru:"default=true,string=Active"` Sequence sdk.Integer `sumeru:"default=10,string=Sequence"`}
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <!-- Window Action --> <action id="action_my_module" type="window" model="my.module" name="My Module" view_mode="list,form,kanban"> <help> <p class="sum-view-nocontent-smiling-face"> Create your first My Module! </p> </help> </action> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_my_module_form" model="my.module" type="form"> <header> <button name="action_confirm" string="Confirm" type="object" class="sum_highlight"/> <field name="active" widget="statusbar"/> </header> <sheet> <div class="sum_title"> <h1> <field name="name" placeholder="Title..."/> </h1> </div> <group> <group> <field name="sequence" string="Sequence"/> </group> <group> <field name="active" string="Active"/> </group> </group> <notebook> <page string="Description" name="description"> <field name="description" placeholder="Internal notes..."/> </page> </notebook> </sheet> </view> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_my_module_list" model="my.module" type="list"> <field name="sequence" widget="handle"/> <field name="name"/> <field name="active" widget="boolean_toggle"/> </view> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_my_module_kanban" model="my.module" type="kanban"> <field name="name"/> <templates> <t t-name="kanban-box"> <div class="sum-kanban-global-click"> <div class="sum-kanban-details"> <strong class="sum-kanban-record-title"> <field name="name"/> </strong> <div> <field name="description"/> </div> </div> </div> </t> </templates> </view> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <!-- sys.menu.name is globally unique; each label must differ from other addons (e.g. base "Configuration"). --> <menuitem id="menu_my_module_root" name="My Module" sequence="20" web_icon="shield" /> <menuitem id="menu_my_module_category" name="My module organization" parent="menu_my_module_root" sequence="100" /> <menuitem id="menu_my_module_action" name="My module records" parent="menu_my_module_category" action="action_my_module" sequence="10" /> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <!-- Module Category --> <record id="module_category_my_module" model="sys.module.category"> <field name="name">My Module</field> <field name="sequence">100</field> </record> <!-- User Group --> <record id="group_my_module_user" model="core.group"> <field name="name">User</field> <field name="category_id" ref="module_category_my_module"/> <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/> </record> <!-- Manager Group --> <record id="group_my_module_manager" model="core.group"> <field name="name">Manager</field> <field name="category_id" ref="module_category_my_module"/> <field name="implied_ids" eval="[(4, ref('group_my_module_user'))]"/> </record> </data></sumeru>
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlinkaccess_my_module_user,my_module.user,my.module,group_my_module_user,1,0,0,0access_my_module_manager,my_module.manager,my.module,group_my_module_manager,1,1,1,1
Compile and run
- Ensure
./addonsis onaddons_pathinsumeru.conf. make generaterefreshesaddonimports/zimports.go.- Install with
-i, then serve onhttp://localhost:8080.
make generatego run . -- -c sumeru.conf -i my_module --stop-after-initmake run
What not to do
- Do not put client modules inside
sumeru/orsumeru_addons/. Keep those pull-only. - Do not skip
make generateafter adding or removing an addon folder. - Do not call
RegisterModel/ implementModelName()in model files —zmodels.gois generated. - Do not hand-edit
zmodels.goorzrefs.go. - Do not ship scaffold groups/CSV without reviewing who can write and unlink.
Next step
Map the three repos in Project structure. Workspace Makefile detail: Custom addons workspace.