Pre-alpha. No tagged release and no upgrade path between versions. Use for evaluation and development only, not production.
DevelopersPre-alpha

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.

addons/engagement_cookbook/models/models.go (excerpt)
go
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:

CategoryGo typesPostgreSQL
StringsString, Text, HTML, Email, Phone, URL, UUIDVARCHAR, TEXT
NumbersInteger, Float, Float64, Numeric, MoneyBIGINT, REAL, DOUBLE PRECISION, NUMERIC
Other scalarsBoolean, Date, Time, DateTime, Duration, Json, Binary, ImageBOOLEAN, DATE, TEXT, TIMESTAMPTZ, NUMERIC, JSONB
SelectionSelection[T ~string]VARCHAR with options from const blocks
RelationsMany2One[T], One2Many[T], Many2Many[T], Reference, Many2OneReferenceFK / join table / polymorphic
Derivedrelated=, compute=, storeRead-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 / Text / HTML / URL
go
// 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 / Phone / UUID
go
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.

Boolean
go
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.

Integer
go
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.

Float / Numeric
go
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.

Money + currency
go
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 + models.go
go
// 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.

Date / DateTime / Time / Duration
go
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).

Json / Binary / Image
go
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.

Many2One
go
// 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=.

One2Many / Many2Many
go
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=.

Reference / Many2OneReference
go
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= mirrors a field through a relation. compute= names a handler registered with orm.RegisterCompute; add store to persist computed values.

Related / compute tags
go
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.

  1. Add "depends": ["base", "hr", "mail"] (or whichever modules you reference).
  2. Run make generate.
  3. Declare EmployeeID sdk.Many2One[HrEmployee] in models.go.

Child models point back with ondelete=cascade on the parent's FK:

addons/engagement_cookbook/models/my_module_line.go
go
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 + computed.go
go
// 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:

TagPurpose
requiredNOT NULL / required in UI
unique, indexDatabase 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, readonlyComputed 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

Terminal
shell
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 generate after adding a model struct or changing depends.
  • Do not hand-edit zmodels.go, zrefs.go, or generated init.go.
  • Do not invent field types outside sumeru/core/sdk markers.
  • Do not put business logic that must survive restarts only in struct methods the ORM never calls.

See also

Next step

Bind screens in Views & menus.