Models & fields
sdk.Model tags, field markers, relations, and how runtime records are maps.
sdk.Model and generate
Prefer sumeru/core/sdk over importing sumeru/core/orm directly. Embed sdk.Model with a sumeru:"model=…" tag, or extend an existing model with sumeru:"inherit=…" — see Model inheritance. After adding a struct, run make generate so models/zmodels.go registers it. Full field cookbook: engagement_cookbook models and the Engagements cookbook.
type MyModule struct {
sdk.Model `sumeru:"model=my.module"`
Name sdk.String `sumeru:"required,unique,index,string=Name"`
Description sdk.Text `sumeru:"string=Description"`
Active sdk.Boolean `sumeru:"string=Active,default=true"`
}
Field types
Declare fields with typed markers from sumeru/core/sdk. Schema sync maps them to PostgreSQL columns on install/update. Quick reference:
| Category | Go types | PostgreSQL |
|---|---|---|
| Strings | String, Text, HTML, Email, Phone, URL, UUID | VARCHAR, TEXT |
| Numbers | Integer, Float, Float64, Numeric, Money | BIGINT, REAL, DOUBLE PRECISION, NUMERIC |
| Other scalars | Boolean, Date, Time, DateTime, Duration, Json, Binary, Image | BOOLEAN, DATE, TEXT, TIMESTAMPTZ, NUMERIC, JSONB |
| Selection | Selection[T ~string] | VARCHAR with options from const blocks |
| Relations | Many2One[T], One2Many[T], Many2Many[T], Reference, Many2OneReference | FK / join table / polymorphic |
| Derived | related=, compute=, store | Read-only mirror or computed column |
See also Field types reference for SQL mapping details.
String variants
Short text, long text, rich HTML, and URL fields. Use size=, column=, required, unique, index.
// String variants — VARCHAR (default 255); use size= for length
Name sdk.String `sumeru:"required,unique,index,string=Name"`
ShortCode sdk.String `sumeru:"size=32,string=Short Code"`
ExternalCode sdk.String `sumeru:"column=external_ref,index,string=External Code"`
Description sdk.Text `sumeru:"string=Description"`
Notes sdk.HTML `sumeru:"string=Notes"`
Website sdk.URL `sumeru:"string=Website"`
Contact and identity
Validated email and phone types; UUID with default=uuid for server-generated IDs.
Email sdk.Email `sumeru:"string=Email"`
Phone sdk.Phone `sumeru:"string=Phone"`
PublicID sdk.UUID `sumeru:"string=Public ID,default=uuid,unique"`
Boolean
True/false flags with optional default=true or false.
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"`
Integers
Whole numbers with optional min= / max= bounds.
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"`
Float and numeric
Float for approximate values; Numeric for exact decimals with precision= and scale=. Use Float64 when you need 64-bit floating point.
Amount sdk.Float `sumeru:"string=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"`
// Float64 is also available for DOUBLE PRECISION columns when you need 64-bit floats.
Money
Monetary amounts tied to a currency many2one via currency=CurrencyID.
Subtotal sdk.Money `sumeru:"string=Subtotal,currency=CurrencyID"`
TaxAmount sdk.Money `sumeru:"string=Tax,currency=CurrencyID"`
CurrencyID sdk.Many2One[CoreCurrency] `sumeru:"string=Currency"`
Selection
Typed selections: define a string type and const options in selection_types.go, then use sdk.Selection[T]. Options are discovered at make generate — no manual registration.
// selection_types.go
type Priority string
const (
PriorityLow Priority = "low"
PriorityNormal Priority = "normal"
PriorityHigh Priority = "high"
)
// models.go — options discovered from const blocks at generate time
Priority sdk.Selection[Priority] `sumeru:"string=Priority,default=normal"`
State sdk.Selection[State] `sumeru:"required,string=Status,default=draft"`
Kind sdk.Selection[Kind] `sumeru:"string=Kind,default=record"`
Date, time, and duration
Calendar dates, timestamps, time-of-day, and intervals.
DateStart sdk.Date `sumeru:"string=Start Date"`
DatetimeDue sdk.DateTime `sumeru:"string=Due Date,index"`
OpeningTime sdk.Time `sumeru:"string=Opening Time"`
ProcessingTime sdk.Duration `sumeru:"string=Processing Time"`
Json, binary, and image
Structured JSON (JSONB), file attachments (Binary), and image fields (Image).
Settings sdk.Json `sumeru:"string=Settings"`
MetadataJson sdk.Json `sumeru:"string=Metadata"`
Document sdk.Binary `sumeru:"string=Document"`
Avatar sdk.Image `sumeru:"string=Avatar"`
Many2one
Foreign key to another model. Same-module targets use the struct type directly; cross-module targets use aliases from zrefs.go. Set ondelete=set_null or cascade as needed.
// Same module
ParentID sdk.Many2One[MyModule] `sumeru:"column=parent_id,string=Parent,ondelete=set_null"`
// Cross-module — types from generated zrefs.go after depends + make generate
CompanyID sdk.Many2One[CoreCompany] `sumeru:"string=Company"`
EmployeeID sdk.Many2One[HrEmployee] `sumeru:"string=Employee"`
One2many and many2many
One2Many is the inverse side of a many2one — use inverse=ParentID on the parent. Many2Many requires an explicit join table with table=, left=, right=.
LineIds sdk.One2Many[MyModuleLine] `sumeru:"string=Lines"`
ChildIds sdk.One2Many[MyModule] `sumeru:"inverse=ParentID,string=Children"`
TagIds sdk.Many2Many[MyModuleTag] `sumeru:"table=my_module_tag_rel,left=module_id,right=tag_id,string=Tags"`
Reference fields
Polymorphic links: a model name string plus Many2OneReference with model_field=.
ResourceRef sdk.Reference `sumeru:"string=Resource Ref"`
ResourceModel sdk.String `sumeru:"string=Resource Model"`
ResourceID sdk.Many2OneReference `sumeru:"model_field=ResourceModel,string=Resource ID"`
Related and computed (model tags)
related= mirrors a field through a relation. compute= names a handler registered with orm.RegisterCompute; add store to persist computed values.
CompanyName sdk.String `sumeru:"related=company_id.name,string=Company Name"`
ComputedAmount sdk.Float `sumeru:"compute=computed_amount,string=Computed Amount"`
StoredLineCount sdk.Integer `sumeru:"compute=stored_line_count,store,readonly,string=Line Count"`
Relations and zrefs
Cross-module many2one fields require the target module in manifest.json depends, then make generate to rewrite models/zrefs.go with typed aliases (CoreCompany, HrEmployee, …). Use those types in your hand-written model structs — do not edit zrefs.go.
- Add
"depends": ["base", "hr", "mail"](or whichever modules you reference). - Run
make generate. - Declare
EmployeeID sdk.Many2One[HrEmployee]inmodels.go.
Child models point back with ondelete=cascade on the parent's FK:
type MyModuleLine struct {
sdk.Model `sumeru:"model=my.module.line"`
ModuleID sdk.Many2One[MyModule] `sumeru:"required,index,string=Module,ondelete=cascade"`
Name sdk.String `sumeru:"required,unique,string=Description"`
Quantity sdk.Integer `sumeru:"string=Quantity,default=1"`
UnitPrice sdk.Numeric `sumeru:"string=Unit Price,default=0"`
}`
Computed fields
Register handlers in init() (typically computed.go). The compute= tag on the model field must match the registered name. Non-stored computes are filled on read; store writes the value to the database when dependencies change.
// models.go
ComputedAmount sdk.Float `sumeru:"compute=computed_amount,string=Computed Amount"`
StoredLineCount sdk.Integer `sumeru:"compute=stored_line_count,store,readonly,string=Line Count"`
// computed.go
func init() {
orm.RegisterCompute("my.module", "computed_amount", []string{"amount", "quantity"}, computeAmount)
orm.RegisterCompute("my.module", "stored_line_count", []string{"line_ids"}, computeLineCount)
}
Struct tags (FieldDefinition)
Common sumeru:"…" tags inferred at registration:
| Tag | Purpose |
|---|---|
required | NOT NULL / required in UI |
unique, index | Database constraints |
default= | Default value (uuid for UUID fields) |
string= | Human label |
column= | Override SQL column name |
size= | VARCHAR length (String) |
precision=, scale= | Numeric decimal places |
min=, max= | Integer bounds |
currency= | Currency many2one field for Money |
inverse= | One2Many → parent Many2One field name |
table=, left=, right= | Many2many join table columns |
ondelete= | FK behaviour (set_null, cascade, …) |
model_field= | Polymorphic model name field for Many2OneReference |
related= | Dot-path through a relation (company_id.name) |
compute=, store, readonly | Computed field wiring |
groups= | Restrict field to ACL group xml id |
Runtime records are maps
ORM reads and writes use map[string]interface{} keyed by field name (plus id). Schema sync follows generated model metadata from struct tags.
Compile and run
make generate
make install MODULES=engagement_cookbook
make run
Schema sync. Install/update syncs columns from field metadata. Review migrations on shared databases. Sumeru is pre-alpha.
What not to do
- Do not skip
make generateafter adding a model struct or changingdepends. - Do not hand-edit
zmodels.go,zrefs.go, or generatedinit.go. - Do not invent field types outside
sumeru/core/sdkmarkers. - Do not put business logic that must survive restarts only in struct methods the ORM never calls.
See also
- Field types reference — SQL mapping
- Field attributes — view XML
- SWC widget catalog — UI widgets per type
- Engagements cookbook — live examples for every type
Next step
Bind screens in Views & menus.