Skip to content

Conventions

LiteORM’s orm front-end leans on convention so a plain struct maps to a table with no ceremony — but every convention is one you can reason about, and every one has an explicit override. There’s no silent pluralization, no lazy loading, and an inference that can’t be made is a hard error naming both sides rather than a best-effort guess. This page lists the mapping rules and how to override each.

For exhaustive API detail, see the reference at pkg.go.dev/liteorm.org/orm.

By default the table name is the snake_case of the type name — Post maps to post, UserProfile to user_profile. LiteORM does not pluralize by default. Override, from most specific to least:

I want… Do this
one type’s table name set exactly give it a TableName() string method
gorm-style plurals process-wide (Postposts) call orm.UsePluralTableNames(true) once at startup
an irregular plural the pluralizer misses orm.RegisterPlural("quiz", "quizzes")
func (Post) TableName() string { return "posts" } // always wins

A per-type TableName() always beats the process-wide setting. UsePluralTableNames applies to both the orm and query front-ends (they share the naming resolver), so call it once before any schema is resolved; RegisterPlural inflects only the last snake_case segment, so register the bare word (person, which also covers blog_person).

A field named ID (column id) is the primary key by convention. An integer primary key is treated as auto-increment — the database assigns it and Create reads the generated value back into your struct. Override:

I want… Do this
a different column as the key tag it orm:"pk"
a non-id field named the key tag it orm:",pk" (or give it column id)
force auto-increment on a key tag it orm:"autoincrement"
a caller-assigned integer key (no auto-increment) tag it orm:"pk,noauto"
a key spanning several columns tag each field orm:"pk" — see composite keys

A composite key is never auto-increment: its parts are yours to assign. Schema.PK is the single-key convenience (nil for a composite key); Schema.PKs is always the full, ordered list.

For an association, the foreign key is inferred by convention as <type>_id in snake_case, on whichever side owns the key — a belongs-to puts <target>_id on the owner (Post.Authorauthor_id on Post), and a has-many or has-one puts <owner>_id on the target (Author.Postsauthor_id on Post). A key that can’t be found is a hard error naming the column it looked for. Override with tags on the relation field:

type Post struct {
ID int64
Author *Author `orm:"fk:writer_id"` // FK column is writer_id, not author_id
Editor *Author `orm:"fk:editor_id,references:ID"` // and the key it references
}

fk: names the foreign-key column, references: names the referenced key column. The referenced column defaults to the target’s primary key. (These tags are read against whichever side owns the key.)

A model becomes soft-delete-aware when it has a field tagged soft_delete — conventionally a sql.NullTime named DeletedAt:

DeletedAt sql.NullTime `orm:"deleted_at,soft_delete"`

There is no naming magic here — the tag is the switch, so the column can be called anything. Its presence turns Delete into an UPDATE, excludes deleted rows from reads by default, and makes AutoMigrate build unique indexes as partial indexes scoped to live rows. To make a model not soft-delete-aware, leave the tag off. The full behavior — the tri-state read scopes, ForceDelete, Restore — is in soft delete.

autocreatetime and autoupdatetime opt a time.Time, sql.NullTime, or *time.Time field into automatic stamping — created-time on Create, updated-time on Create and Update. These are explicit tags, not name-based magic; a field named CreatedAt does nothing special until you tag it:

CreatedAt time.Time `orm:"created_at,autocreatetime"`
UpdatedAt time.Time `orm:"updated_at,autoupdatetime"`