Skip to content
Column Types Reference

Column Types Reference

Methods

Every method on *migration.Table that declares a column, with the type each driver gets. The generic name in the first column is the ColType* constant, which you only need when calling AddColumn directly.

Integers

MethodPostgreSQLMySQLSQLite
Increments(name)SERIALINTINTEGER
BigIncrements(name)BIGSERIALBIGINTINTEGER
TinyInt(name)SMALLINTTINYINTTINYINT
SmallInt(name)SMALLINTSMALLINTSMALLINT
MediumInt(name)INTEGERMEDIUMINTMEDIUMINT
Int(name)INTEGERINTINTEGER
BigInt(name)BIGINTBIGINTBIGINT

UnsignedTinyInt, UnsignedSmallInt, UnsignedMediumInt, UnsignedInt and UnsignedBigInt are the same types with Unsigned() applied — which adds UNSIGNED on MySQL and nothing on PostgreSQL or SQLite.

There is no Integer or BigInteger. The methods are Int and BigInt.

Decimals

MethodPostgreSQLMySQLSQLite
Float(name, precision, scale)FLOATFLOATFLOAT
Double(name, precision, scale)DOUBLE PRECISIONDOUBLEDOUBLE
Decimal(name, precision, scale)DECIMALDECIMALDECIMAL

Pass 0, 0 to Double unless you mean a precision — see the note under dates.

Text

MethodPostgreSQLMySQLSQLite
Char(name, length)CHARCHARCHAR
String(name, length)VARCHARVARCHARVARCHAR
Text(name)TEXTTEXTTEXT
Json(name)JSONBJSONTEXT
UUID(name)UUIDCHARTEXT
ULID(name)CHARCHARTEXT

Json gives JSONB on PostgreSQL rather than JSON: it stores a parsed form, so it is cheaper to read than to write and can carry a GIN index. The cost is that key order is not preserved and duplicate keys collapse — both properties of JSON objects rather than of this choice. SQLite has no JSON type; it stores JSON as TEXT and its JSON1 functions operate on TEXT, so that is the answer rather than a fallback.

MySQL cannot index a JSON column at all, and no key length helps. Validate says so, pointing at a generated column over the extracted value. The same check covers TEXT and BLOB columns, which MySQL refuses in a key without a length.

Dates and times

MethodPostgreSQLMySQLSQLite
Date(name)DATEDATEDATE
Time(name, precision)TIMETIMETIME
DateTime(name, precision)TIMESTAMPDATETIMEDATETIME
DateTimeTz(name, precision)TIMESTAMP WITH TIME ZONEDATETIMEDATETIME
Timestamp(name, precision)TIMESTAMPTIMESTAMPTIMESTAMP
TimestampTz(name, precision)TIMESTAMP WITH TIME ZONETIMESTAMPTIMESTAMP

Pass 0 for the precision on the Tz variants. It is appended after the whole type name, so TimestampTz("created_at", 6) emits TIMESTAMP WITH TIME ZONE(6) and PostgreSQL answers syntax error at or near "(". With 0 you get the type and no suffix, which is what was wanted. DateTime and Timestamp take a precision safely, because theirs is one word.

Other

MethodPostgreSQLMySQLSQLite
Boolean(name)BOOLEANBOOLEANBOOLEAN
Binary(name)BYTEABINARYBINARY
Enum(name, values...)TEXT + CHECKENUM(...)TEXT

Enum enforces nothing on SQLite. MySQL gets a native ENUM and PostgreSQL a TEXT with a CHECK (col IN (...)) appended, but SQLite gets plain TEXT and no constraint at all — so the values are checked on two drivers of three. Use a String and a Check when you need all three:

t.String("status", 16).Default("draft")
t.Check("posts_status_valid", "status IN ('draft','published','removed')")

Timestamps and soft deletes

There are no Timestamps() or SoftDeletes() shortcuts. Declare them:

t.TimestampTz("created_at", 0).Default(migration.Expr("now()"))
t.TimestampTz("updated_at", 0).Default(migration.Expr("now()"))
t.TimestampTz("deleted_at", 0).Nullable()

A soft-delete column must be nullable, and the model field must be *time.Time or sql.NullTime — the ORM rejects a bare time.Time at metadata time, because its zero value writes a real timestamp and deleted_at IS NULL would then match nothing and hide the whole table. See Soft deletes.