Databases
Steward runs on SQLite, MySQL and PostgreSQL. steward new takes the engine
as a flag:
steward new myadmin --module example.com/myadmin --db postgresEverything the framework does — grids, filters, relation paths, uploads, migrations, the search index — works the same on all three. What follows is the short list of places where the engine is still visible.
Quick search is case-insensitive everywhere
A grid’s search box and a LIKE filter match regardless of case. MySQL gets
that from its collation and SQLite from its ASCII case folding; on PostgreSQL,
where LIKE is case-sensitive, Steward emits ILIKE instead. Searching
riverton finds Riverton on every engine.
Any column type, not only text
QuickSearch and Like read a column of any type — uuid, numeric,
timestamptz — and not only the text ones:
type MediaFile struct {
ID string `gorm:"type:uuid;primaryKey"`
SubmissionID string `gorm:"type:uuid;index"`
FieldKey string `gorm:"size:64"`
}
g.QuickSearch("SubmissionID", "FieldKey") // both are searchedPostgreSQL defines its pattern operators for text alone and casts nothing to it
implicitly, so Steward casts the column there: CAST(col AS TEXT) ILIKE ?.
Searching part of a uuid finds the row. The other engines compare the column as
it is.
Equality is not cast — SubmissionID:6f1e7c7e-… still compares uuid = uuid
and still uses the index.
Note
A pattern search cannot use a btree index on any engine: %term% has no
prefix to seek on, and PostgreSQL’s case-insensitive ILIKE rules out the
varchar_pattern_ops index that would otherwise serve term%. On a table
large enough for that to matter, give the panel a
search engine — quick search and the palette go
through it instead, and the SQL fallback is what runs only when none is
configured.
PostgreSQL: set the timezone in your DSN
PostgreSQL stores time.Time as timestamptz and reads a bare date — the
2026-07-31 a date filter compares against — in the session’s timezone. A
server session on UTC behind an application running on WIB moves every date
boundary by seven hours, so “on the 31st” quietly means “from 07:00 on the
31st to 07:00 on the 1st”.
Name the application’s zone in the DSN and the two agree:
dsn := "postgres://user:pass@localhost:5432/myadmin?sslmode=disable&TimeZone=Asia/Jakarta"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})Use the IANA name (Asia/Jakarta), not the abbreviation (WIB) — the server
rejects the latter.
PostgreSQL: AutoMigrate wants the owning model first
If you use AutoMigrate for your own tables rather than
migrations, pass the model that declares a many2many
before the models on the other side of it:
db.AutoMigrate(&Article{}, &Tag{}) // Article declares the join tableThe join table carries foreign keys to both sides, and PostgreSQL refuses to create it before the tables it points at exist. SQLite accepts either order, so a schema built by AutoMigrate can work locally and fail on deployment. Migrations do not have this problem: they run in the order you wrote them.
What Steward stores
The framework’s own tables (users, roles, permissions, settings, jobs, the
search index) use types every engine has. There is no engine-specific column
in the framework, so steward migrate runs unchanged on all three.