Views & menus
How XML views bind to model names, menu trees and when you need -u.
How UI is wired
Every screen a user opens is three XML pieces working together:
- Views: how one model looks (
tree/form/kanban). - Action: which model to open and in which view modes.
- Menu: where that action appears in the sidebar.
List the files in manifest.json -> data in that order (security -> actions -> views -> menus). Root element is always <sumeru><data>...</data></sumeru>.
Pre-alpha. Attribute names below match shipping sumeru / sumeru_addons XML. Do not assume attributes work unless listed here.
Quick map
| Goal | Write | Next |
|---|---|---|
| A list of records | <view type="tree"> | Point an action's view_mode at tree |
| A detail screen | <view type="form"> | Include form in view_mode |
| Cards board | <view type="kanban"> + <templates> | Add kanban to view_mode |
| Sidebar entry | <menuitem action="..."/> | Action id must load before the menu |
| Extend another module's UI | <record model="sys.view"> + <xpath> | Run -u on your module |
<view>
Primary UI for one model. The engine stores the inner XML as arch on sys.view.
<view id="VIEW_XML_ID" model="dotted.model" type="tree|form|kanban|pivot" open="0"?> ...fields / layout...</view>
id required
Syntax. id="view_my_module_tree". Letters, digits, _, ..
Effect. Stable XML id. Other modules inherit with ref="my_module.view_my_module_tree". Must be unique inside the module.
model required
Syntax. model="my.module". Exact string from Go ModelName().
Effect. Binds every nested <field name="..."/> to that model's schema. Wrong model -> missing columns / empty UI.
type required
Syntax. type="tree" | form | kanban | pivot.
Effect. Chooses the renderer. tree = list columns; form = sheet layout; kanban needs <templates>; pivot = read-only aggregation table (v1).
open optional
Syntax. On trees only: open="0".
Effect. Disables click-row -> open form. Use for read-only report lists (e.g. invoice analysis).
<view id="view_account_invoice_report_tree" model="account.invoice.report" type="tree" open="0"> <field name="partner_id" string="Customer"/> <field name="amount_total" string="Total"/></view>
<field> in views
Inside <view>, each <field> maps one model field into the UI. Default widget comes from the field type; override with widget.
<field name="field_name" string="Label"? widget="widget_key"? placeholder="Hint..."? options="{'key': 'value'}"? readonly="1"? invisible="1"?/>
name required
Syntax. name="active". Must match a key in Fields().
Effect. Selects which DB/ORM value to read and write. Unknown names are ignored or break render.
string optional
Syntax. string="Active".
Effect. Overrides the label from the model's String / field definition for this view only. Tree column header and form label use it.
widget optional
Syntax. widget="boolean_toggle" (see Widgets).
Effect. Swaps the default control (plain text/checkbox) for a specialised renderer. Wrong widget for the type -> odd or empty UI.
placeholder optional
Syntax. placeholder="e.g. SO001".
Effect. Grey hint inside empty inputs. Does not set a default value and is not stored.
options optional
Syntax. JSON-ish string: options="{'clickable': '1'}" (single quotes as in shipping XML).
Effect. Widget-specific knobs — statusbar clickable, priority mode:stars, many2one domain:[…]. See SWC widget catalog.
readonly optional
Syntax. readonly="1".
Effect. Field displays but the user cannot edit it in this view (e.g. computed timestamps, key prefixes).
required optional
Syntax. required="1".
Effect. Client-side required validation before save (merged with model-level Required from Go Fields()). See Field attributes.
invisible optional
Syntax. invisible="1".
Effect. Field stays in the arch (still loaded) but is not shown. Use sparingly in pre-alpha.
<field name="name" string="Product" placeholder="Product Name..."/><field name="active" string="Active" widget="boolean_toggle"/><field name="key_prefix" string="Prefix" readonly="1"/>
<field> in <record>
Different job: set a column on a data row (groups, inherit views, defaults), not draw a widget.
<record id="XML_ID" model="some.model"> <field name="column">literal text</field> <field name="rel_id" ref="other.module.xml_id"/> <field name="m2m" eval="[(4, ref('some.group'))]"/> <field name="arch" type="xml">...embedded xml...</field></record>
name
Effect. Target column on model.
ref
Syntax. ref="base.group_user" or same-module id.
Effect. Writes the database id of that XML record (Many2one-style links).
eval
Syntax. eval="[(4, ref('group_my_module_user'))]".
Effect. Evaluated expression. Common for M2M link commands like [(4, ref(...))].
type="xml"
Effect. Treats child nodes as XML arch (required for inherit arch blobs).
Widgets
Omit widget for the default control. Set it when you need a specific interaction.
boolean_toggle
Use on. Boolean fields. Effect. Switch control instead of a plain checkbox. Works in trees and forms.
<field name="active" string="Active" widget="boolean_toggle"/>
handle
Use on. Integer sequence (or similar) columns in trees. Effect. Drag handle so users reorder rows; writes the sequence field.
<view type="tree" ...> <field name="sequence" widget="handle"/> <field name="name"/></view>
statusbar
Use on. Selection / state fields, usually inside <header>. Effect. Shows stages as chips. With options="{'clickable': '1'}", users can click a stage to write the field (sale / purchase / CRM).
<header> <field name="state" widget="statusbar" options="{'clickable': '1'}"/></header>
selection
Use on. Selection or Many2one fields that should look like a dropdown. Effect. Select control (many2one uses the selection-style UI / cascade helpers where wired).
<field name="country_id" string="Country" widget="selection"/>
email
Use on. Char email fields. Effect. Email-oriented input/display (company / partner forms).
<field name="email" string="Email" widget="email"/>
priority
Use on. Priority / ranking selection or int. Effect. Compact priority control (stars / rank) instead of a raw number.
<field name="priority" widget="priority"/>
mail_thread
Use on. A text / related field inside <chatter>. Effect. Hooks the activity / messages panel to that field.
<chatter> <field name="internal_notes" string="Communication" widget="mail_thread"/></chatter>
<button>
Place in <header> (or sheet). Click runs server-side logic named by name.
<button name="method_name" string="Label" type="object" class="sum_highlight"?/>
name required
Effect. Method to invoke on the current record (e.g. action_post, action_confirm). Must exist on the model / addon code path.
string required
Effect. Visible button label.
type required
Syntax. type="object" in all current samples.
Effect. Calls a method on the record. Not a URL, not a window action.
class optional
Syntax. class="sum_highlight" (or empty).
Effect. Visual emphasis for the primary action in the header.
<header> <button name="action_post" string="Post" type="object" class="sum_highlight"/> <button name="action_register_payment" string="Register Payment" type="object"/> <button name="action_cancel" string="Cancel" type="object"/> <field name="state" widget="statusbar"/></header>
Form layout
Forms are nested containers. XML order is screen order.
<header>
Effect. Top chrome: buttons + statusbar. Keep workflow actions here.
<sheet>
Effect. Main body card. Put title, groups, notebooks inside.
<group>
Attributes. string="Address" (section title), colspan="2" (span full width).
Effect. Nested <group><group>...</group><group>...</group></group> builds a two-column grid.
<notebook> / <page>
Page attributes. string = tab label; name = optional technical tab id.
Effect. Tabs under the sheet. Only one page visible at a time.
<div class="sum_title">
Effect. Title block styling. Put the primary name field in an <h1>.
<chatter>
Effect. Hosts widget="mail_thread" beside/under the form (messages / log).
<sheet> <div class="sum_title"> <h1><field name="name" placeholder="Company legal name..."/></h1> </div> <group> <group string="Identification"> <field name="vat" string="Tax ID"/> </group> <group string="Contact"> <field name="email" string="Email" widget="email"/> </group> </group> <notebook> <page string="Internal Notes" name="notes"> <field name="internal_notes"/> </page> </notebook></sheet><chatter> <field name="internal_notes" string="Communication" widget="mail_thread"/></chatter>
<action>
A window action is what menus point at. It does not draw fields; it chooses the model and view modes.
<action id="action_my_module" type="window" model="my.module" name="My Module" view_mode="tree,form,kanban"> <help>...optional empty-state HTML...</help></action>
id required
Effect. Target of <menuitem action="..."/>.
type required
Syntax. type="window".
Effect. Opens a model workspace (list/form/kanban switcher).
model required
Effect. Which model's views to load. Must match view model values.
name required
Effect. Title shown in the workspace chrome.
view_mode required
Syntax. Comma-separated, order = default tab order: tree,form,kanban.
Effect. Only listed modes appear. First mode is the landing view.
<help> optional
Effect. Empty-list placeholder. Class sum-view-nocontent-smiling-face is the usual styling hook.
<menuitem>
Build a tree: root (app) -> section -> leaf with action.
<menuitem id="menu_id" name="Visible label" sequence="10" parent="parent_menu_id"? action="action_xml_id"? web_icon="shield"? groups="module.group_xml_id"?/>
id required
Effect. XML id for parent="..." links.
name required
Effect. Sidebar label. Must be globally unique across addons (DB constraint on sys.menu.name). Prefer "My module records", not a second "Configuration".
sequence required in practice
Syntax. Integer, lower = higher in the list.
Effect. Sort order among siblings.
parent optional
Effect. Nest under another menu. Omit on app roots.
action optional
Effect. Leaf click opens that window action. Section nodes omit it.
web_icon optional
Syntax. Sprite key: shield, package, settings, users, file-text, shopping-cart, ...
Effect. Icon in the app switcher / sidebar (menu-icons.svg#i-KEY).
groups optional
Syntax. groups="account.group_account_invoice".
Effect. Menu is hidden unless the user belongs to that security group.
<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"/>
End-to-end examples
Tree view
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_my_module_tree" model="my.module" type="tree"> <field name="sequence" widget="handle"/> <field name="name"/> <field name="active" widget="boolean_toggle"/> </view> </data></sumeru>
Drag handle reorders via sequence; toggle flips active; click a row opens the form (unless open="0").
Form view
<?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>
Kanban view
Declare preload fields, then the card template. t-name="kanban-box" is the card root.
<?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>
Pivot view
Set type="row", type="col", or type="measure" on nested <field> elements. The engine aggregates up to 500 rows from the action domain into a read-only HTML table. No CSV/PDF export or drill-down in v1.
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <view id="view_sale_order_pivot" model="sale.order" type="pivot"> <field name="partner_id" type="row"/> <field name="date_order" type="col"/> <field name="amount_total" type="measure"/> </view> </data></sumeru>
Include pivot in the window action view_mode, e.g. tree,form,pivot.
Action + menus
<?xml version="1.0" encoding="utf-8"?><sumeru> <data> <action id="action_my_module" type="window" model="my.module" name="My Module" view_mode="tree,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> <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 inheritance
Change another module's arch without copying the whole view.
<record id="view_..._inherit" model="sys.view"> <field name="name">technical.name</field> <field name="model">dotted.model</field> <field name="inherit_id" ref="module.parent_view_xml_id"/> <field name="arch" type="xml"> <xpath expr="//field[@name='TARGET']" position="after|before|replace|inside"> ...nodes to insert or replace... </xpath> </field></record>
inherit_id + ref
Effect. Parent view to patch (must already exist).
expr
Syntax. Supported today: //field[@name='...'].
Effect. Locates the anchor field in the flattened arch.
position
Values. after | before | replace | inside.
Effect. Where the xpath body is applied relative to the match (inside appends before </view> in the flattened arch).
<record id="view_my_module_tree_inherit" model="sys.view"> <field name="name">my.module.tree.inherit</field> <field name="model">my.module</field> <field name="inherit_id" ref="my_module.view_my_module_tree"/> <field name="arch" type="xml"> <xpath expr="//field[@name='name']" position="after"> <field name="description"/> </xpath> </field></record>
Narrow xpath. Implementation lives in core/engine/viewinherit. Exotic xpath expressions beyond simple field selectors will fail. Stick to //field[@name='...'].
Report exchange on views
Enable CSV/PDF download and bulk CSV import on list, kanban, and form views. Full engine reference: Report engine.
| Declaration | Attributes |
|---|---|
<report download="csv,pdf" upload="bulk" pdf_sizes="a4,legal,letter" modes="create,upsert"/> | Child of <view> (recommended) |
report_download, bulk_upload, pdf_sizes, bulk_modes | View attributes (same meaning) |
<widget type="report_download" formats="csv,pdf"/> | Header widget on form or list |
<widget type="bulk_upload" modes="create,upsert"/> | Header widget for bulk import |
When any capability is enabled, a Report toolbar appears. Pivot views do not support export in v1.
Kanban view attributes
On <view type="kanban"> (or nested <kanban> root):
default_group_by
Syntax. Field name, e.g. stage_id.
Effect. Columns grouped by that field. Falls back to group_by when unset.
group_by
Syntax. Field name.
Effect. Secondary grouping field when default_group_by is empty.
records_draggable
Syntax. 1 / true to enable drag between columns. Disable with false, 0, off, or no.
Effect. Drag-and-drop cards between kanban columns when enabled and user has write access.
quick_create
Syntax. Enable inline create in a grouped kanban column. Disable with 0, false, off, or no (CRM forecast uses quick_create="0").
Effect. Default on when the board is grouped. Quick-create merges action default_* values into the new record.
Profile photo and image fields
widget="image" on a binary or text field renders a file upload control without crop UI.
When a form has both an image field and a <div class="sum_title"> title block, the engine shows an avatar crop wizard (circular preview). Pan/zoom is stored in image_crop as JSON: {"x":0,"y":0,"zoom":1}. User profile forms in Settings use this path.
On kanban cards, image fields render full photos by default. Use widget="circle" for a circular avatar thumbnail.
Field groups attribute
On <field groups="module.group_xml_id">, the field is hidden when the signed-in user lacks that access group (same comma-separated group list as menu groups). Empty or missing groups means visible to everyone who can open the form.
Form validation and toasts
The live workspace is SWC. Toasts come from env.services.notification (top-right stack, close button, hover-pause, 6s auto-dismiss). See SWC notifications.
- Client required check — empty required fields stay inline on the form (
sum-flash). They are not a validation toast. - RPC / network errors — error toast. Successful save, delete, duplicate, and object buttons show a success toast unless the action returns
{close},{open}, or{redirect}. - Kanban — quick-create success toast; create/move RPC failures toast. Successful drag-move stays silent.
- Removed HTTP — do not POST to
/web/record/save(not registered). SWC uses JSON-RPC andenv.services.notification. See HTTP routes.
When to run -u
After any XML / CSV change: update the module so data sync reloads arch and menus.
go run . -- -c sumeru.conf -u my_module --stop-after-initmake run
What not to do
- Root element must be
<sumeru>only — not legacy ERP root tags. - Do not reuse menu
namevalues across addons. - Do not list menus before actions in
data. - Do not invent widget names; use the SWC widget catalog.
- Do not skip
-uafter XML edits and wonder why the UI is stale.
SWC reference
- Field attributes — complete
<field>attribute table - SWC widget catalog — all built-in widgets and options
- View types — list, form, kanban, pivot, graph, calendar
- Build a module with SWC — end-to-end addon walkthrough
- Arch JSON pipeline — how XML becomes workspace JSON
Next step
Configure export and import ACL in Report engine, then gate menus in Security & access.