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=… (minimal layout), or explore the pre-shipped engagement_cookbook kitchen-sink demo for every widget and view type. Declare models with sdk.Model tags, list XML in the manifest, run make generate, then make install.
Scaffold vs demo. make new MODULE=my_app creates one model and starter views. engagement_cookbook is already in the repo — use make install MODULES=engagement_cookbook to explore it; do not run make new MODULE=engagement_cookbook over the reference copy.
Scaffold with sumeru-bp
Use the workspace tab for client work. Name flag aliases: -name, -bp, -boilerplate, -blueprint.
cd sumeru_custom_addonsmake setupmake new MODULE=my_appmake install MODULES=my_app
cd sumerumake setupmake bp NAME=my_appmake 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.
Reference module (kitchen-sink)
Browse the pre-shipped engagement_cookbook demo — six models, all view types, and the full widget set. Click a file in the tree or sidebar. Same layout as sumeru_custom_addons/addons/engagement_cookbook. For a guided tour see Engagements cookbook.
addons/engagement_cookbook/
├──
├──
├── models/
│ ├──
│ └── zmodels.go
├── views/
│ ├──
│ ├──
│ ├──
│ ├──
│ └──
└── security/
├──
└──
{ "name": "engagement_cookbook", "display_name": "Engagements Cookbook", "version": "2.0.0", "author": "Project Meru", "description": "Professional services engagement tutorial: multi-model app, field widgets, view types, module depends, model/view inherit, and event bridges.", "depends": ["base", "contacts", "hr", "mail"], "application": true, "data": [ "security/security.xml", "security/sys.access.csv", "data/demo.xml", "views/form_view.xml", "views/list_view.xml", "views/kanban_view.xml", "views/search_view.xml", "views/graph_view.xml", "views/pivot_view.xml", "views/calendar_view.xml", "views/gantt_view.xml", "views/cohort_view.xml", "views/event_views.xml", "views/place_views.xml", "views/tag_views.xml", "views/category_views.xml", "views/line_views.xml", "views/partner_inherit_views.xml", "views/actions.xml", "views/menus.xml" ]}
// Code generated by sumeru-import-gen; DO NOT EDIT. package engagement_cookbook import ( _ "sumeru_custom_addons/addons/engagement_cookbook/models")
package models import ( "sumeru/core/sdk") // EngagementProject is a client engagement / professional services project.type EngagementProject struct { sdk.Model `sumeru:"model=engagement.project"` Name sdk.String `sumeru:"required,unique,index,string=Engagement Name"` Reference sdk.String `sumeru:"index,string=Reference"` ExternalCode sdk.String `sumeru:"column=external_ref,index,string=External Code"` SerialNumber sdk.String `sumeru:"unique,index,string=Serial Number"` ShortCode sdk.String `sumeru:"size=32,string=Short Code"` Description sdk.Text `sumeru:"string=Description"` Notes sdk.HTML `sumeru:"string=Internal Notes"` Website sdk.URL `sumeru:"string=Website"` Active sdk.Boolean `sumeru:"string=Active,default=true"` Verified sdk.Boolean `sumeru:"string=Verified,default=false"` Archived sdk.Boolean `sumeru:"string=Archived,default=false,index"` Sequence sdk.Integer `sumeru:"string=Sequence,default=10"` Quantity sdk.Integer `sumeru:"string=Quantity,default=1"` ProgressPct sdk.Integer `sumeru:"string=Progress %,default=0,min=0,max=100"` Rating sdk.Integer `sumeru:"string=Rating,min=0,max=5"` Amount sdk.Float `sumeru:"string=Budget Amount,default=0"` Price sdk.Numeric `sumeru:"string=Price,precision=18,scale=2,default=0"` TaxRate sdk.Numeric `sumeru:"string=Tax Rate,precision=20,scale=8,default=0"` Balance sdk.Numeric `sumeru:"string=Balance,precision=40,scale=18,default=0"` Subtotal sdk.Money `sumeru:"string=Subtotal,currency=CurrencyID"` TaxAmount sdk.Money `sumeru:"string=Tax,currency=CurrencyID"` CurrencyID sdk.Many2One[CoreCurrency] `sumeru:"string=Currency"`// …
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <action id="action_engagement_project" type="window" model="engagement.project" name="Engagements" view_mode="kanban,list,form,calendar,gantt"> <help> <p class="sum-view-nocontent-smiling-face"> Create a client engagement to explore workflow, relations, and every workspace view type. </p> </help> </action> <action id="action_engagement_analysis" type="window" model="engagement.project" name="Engagement analysis" view_mode="graph,pivot,cohort,list" /> <action id="action_engagement_deliverable" type="window" model="engagement.deliverable" name="Deliverables" view_mode="list,form" /> <action id="action_engagement_milestone" type="window" model="engagement.milestone" name="Milestones" view_mode="calendar,gantt,list,form" /> <action id="action_engagement_site" type="window" model="engagement.site" name="Client sites" view_mode="map,list,form" /> <action id="action_engagement_tag" type="window" model="engagement.tag" name="Tags" view_mode="list,form" /> <action id="action_engagement_service_line" type="window" model="engagement.service_line" name="Service lines" view_mode="list,form" /> <action id="action_engagement_contacts" type="window" model="core.partner" name="Contacts" view_mode="list,form,kanban"> <help> <p>Contacts form shows fields extended via <code>inherit=core.partner</code> from this module.</p> </help> </action> <action id="action_engagement_tutorial" type="window" model="engagement.project" name="Tutorial" view_mode="list,form"> <help> <p><strong>Engagements Cookbook</strong> depends on <code>base</code>, <code>contacts</code>, <code>hr</code>, and <code>mail</code>.</p> <p>See <code>README.md</code> in this addon for model inherit, view xpath, event hooks, and cross-module relations.</p> </help>// …
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_engagement_project_form" model="engagement.project" type="form"> <header> <button name="action_confirm" string="Activate" type="object" class="sum_highlight" /> <button name="action_done" string="Done" type="object" /> <button name="action_reset_draft" string="Reset to Draft" type="object" /> <field name="state" widget="statusbar" options="clickable:1" /> </header> <sheet> <div class="sum_title"> <field name="avatar" widget="image" /> <h1> <field name="name" widget="char" placeholder="Title..." /> </h1> </div> <group> <group string="Identity"> <field name="reference" widget="default" /> <field name="external_code" /> <field name="serial_number" /> <field name="short_code" /> <field name="public_id" readonly="1" /> <field name="priority" widget="priority" options="mode:stars,stars:5" /> <field name="kind" widget="radio" /> <field name="color_code" widget="color" /> </group> <group string="Flags"> <field name="active" widget="radio" /> <field name="verified" widget="boolean" /> <field name="archived" widget="boolean_toggle" /> <field name="sequence" /> </group> </group>// …
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_engagement_project_list" model="engagement.project" type="list"> <field name="sequence" widget="handle" /> <field name="name" /> <field name="reference" /> <field name="state" /> <field name="kind" /> <field name="priority" widget="priority" options="mode:stars,stars:5" /> <field name="company_id" /> <field name="amount" /> <field name="subtotal" widget="monetary" /> <field name="date_start" /> <field name="date_stop" /> <field name="progress_pct" widget="progressbar" /> <field name="active" widget="boolean_toggle" /> </view> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_engagement_project_kanban" model="engagement.project" type="kanban" default_group_by="state" records_draggable="1"> <field name="state" /> <field name="avatar" widget="image" /> <field name="name" /> <field name="reference" /> <field name="priority" widget="priority" options="mode:stars,stars:5" /> <field name="subtotal" widget="monetary" /> <field name="active" widget="boolean_toggle" /> </view> </data></sumeru>
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <menuitem id="menu_engagement_root" name="Engagements Cookbook" sequence="20" web_icon="briefcase" /> <menuitem id="menu_engagement_operations" name="Operations" parent="menu_engagement_root" sequence="10" /> <menuitem id="menu_engagement_planning" name="Planning" parent="menu_engagement_root" sequence="20" /> <menuitem id="menu_engagement_analytics" name="Analytics" parent="menu_engagement_root" sequence="30" /> <menuitem id="menu_engagement_clients" name="Clients" parent="menu_engagement_root" sequence="40" /> <menuitem id="menu_engagement_configuration" name="Configuration" parent="menu_engagement_root" sequence="90" groups="engagement_cookbook.group_engagement_cookbook_manager" /> <menuitem id="menu_engagement_projects" name="Engagements" parent="menu_engagement_operations" action="action_engagement_project" sequence="10" /> <menuitem id="menu_engagement_deliverables" name="Deliverables" parent="menu_engagement_operations" action="action_engagement_deliverable" sequence="20" /> <menuitem id="menu_engagement_milestones" name="Milestones" parent="menu_engagement_planning" action="action_engagement_milestone" sequence="10" /> <menuitem id="menu_engagement_sites" name="Client sites" parent="menu_engagement_planning" action="action_engagement_site" sequence="20" /> <menuitem id="menu_engagement_analysis" name="Engagement analysis" parent="menu_engagement_analytics" action="action_engagement_analysis" sequence="10" /> <menuitem id="menu_engagement_contacts" name="Contacts (extended)" parent="menu_engagement_clients" action="action_engagement_contacts" sequence="10" /> <menuitem id="menu_engagement_tags" name="Tags" parent="menu_engagement_configuration" action="action_engagement_tag" sequence="10" groups="engagement_cookbook.group_engagement_cookbook_manager" /> <menuitem id="menu_engagement_service_lines" name="Service lines" parent="menu_engagement_configuration" action="action_engagement_service_line" sequence="20" groups="engagement_cookbook.group_engagement_cookbook_manager" /> <menuitem id="menu_engagement_tutorial" name="About / tutorial" parent="menu_engagement_configuration" action="action_engagement_tutorial" sequence="99" groups="engagement_cookbook.group_engagement_cookbook_manager" /> </data>// …
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <record id="module_category_engagement_cookbook" model="sys.module.category"> <field name="name">Engagements Cookbook</field> <field name="sequence">100</field> </record> <record id="group_engagement_cookbook_user" model="core.group"> <field name="name">User</field> <field name="category_id" ref="module_category_engagement_cookbook"/> <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/> </record> <record id="group_engagement_cookbook_manager" model="core.group"> <field name="name">Manager</field> <field name="category_id" ref="module_category_engagement_cookbook"/> <field name="implied_ids" eval="[(4, ref('group_engagement_cookbook_user'))]"/> </record> </data></sumeru>
id,name,model,group_id,perm_read,perm_write,perm_create,perm_unlinkaccess_engagement_project_user,access_engagement_project_user,engagement.project,group_engagement_cookbook_user,1,1,1,0access_engagement_project_manager,access_engagement_project_manager,engagement.project,group_engagement_cookbook_manager,1,1,1,1access_engagement_deliverable_user,access_engagement_deliverable_user,engagement.deliverable,group_engagement_cookbook_user,1,1,1,0access_engagement_deliverable_manager,access_engagement_deliverable_manager,engagement.deliverable,group_engagement_cookbook_manager,1,1,1,1access_engagement_tag_user,access_engagement_tag_user,engagement.tag,group_engagement_cookbook_user,1,1,0,0access_engagement_tag_manager,access_engagement_tag_manager,engagement.tag,group_engagement_cookbook_manager,1,1,1,1access_engagement_service_line_user,access_engagement_service_line_user,engagement.service_line,group_engagement_cookbook_user,1,1,0,0access_engagement_service_line_manager,access_engagement_service_line_manager,engagement.service_line,group_engagement_cookbook_manager,1,1,1,1access_engagement_milestone_user,access_engagement_milestone_user,engagement.milestone,group_engagement_cookbook_user,1,1,1,0access_engagement_milestone_manager,access_engagement_milestone_manager,engagement.milestone,group_engagement_cookbook_manager,1,1,1,1access_engagement_site_user,access_engagement_site_user,engagement.site,group_engagement_cookbook_user,1,1,1,0access_engagement_site_manager,access_engagement_site_manager,engagement.site,group_engagement_cookbook_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 engagement_cookbook --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.