Custom types
Custom types let you define your own column types with custom SQL data types and JS/DB value transforms
MySQL exposes customType from drizzle-orm/mysql-core.
Examples
The best way to see how customType definition is working is to check how existing data types could be defined using customType function from Drizzle ORM.
Int
import { customType, mysqlTable } from "drizzle-orm/mysql-core";
const customInt = customType<{ data: number }>({
dataType() {
return "int";
},
});
export const users = mysqlTable("users", {
id: customInt(),
});Text
import { customType, mysqlTable } from 'drizzle-orm/mysql-core';
const customText = customType<{ data: string }>({
dataType() {
return 'text';
},
});
export const users = mysqlTable("users", {
name: customText(),
});Timestamp
import { customType, mysqlTable } from "drizzle-orm/mysql-core";
const customTimestamp = customType<{
data: Date;
driverData: string;
config: { fsp?: number };
configRequired: true;
}>({
dataType(config) {
const precision = typeof config.fsp !== "undefined" ? ` (${config.fsp})` : "";
return `timestamp${precision}`;
},
fromDriver(value: string): Date {
return new Date(value);
},
});
export const users = mysqlTable("users", {
createdAt: customTimestamp("created_at", { fsp: 4 }),
});Usage for all types will be same as defined functions in Drizzle ORM. For example:
const usersTable = mysqlTable("users", {
id: customInt().primaryKey(),
name: customText().notNull(),
createdAt: customTimestamp("created_at", { fsp: 4 })
.notNull()
.default(sql`now()`),
});TS-doc for type definitions
You can check ts-doc for types, param definition and examples
interface CustomTypeValues {
/**
* Required type for custom column, that will infer proper type model
*
* Examples:
*
* If you want your column to be `string` type after selecting/or on inserting - use `data: string`. Like `text`, `varchar`
*
* If you want your column to be `number` type after selecting/or on inserting - use `data: number`. Like `integer`
*/
data: unknown;
/**
* Type helper, that represents what type database driver is returning for specific database data type
*
* Needed only in case driver's output and input for type differ
*
* Defaults to {@link driverData}
*/
driverOutput?: unknown;
/**
* Type helper, that represents what type database driver is accepting for specific database data type
*/
driverData?: unknown;
/**
* Type helper, that represents what type field returns after being aggregated to JSON
*/
jsonData?: unknown;
/**
* What config type should be used for {@link CustomTypeParams} `dataType` generation
*/
config?: Record<string, any>;
/**
* Whether the config argument should be required or not
* @default false
*/
configRequired?: boolean;
/**
* If your custom data type should be notNull by default you can use `notNull: true`
*
* @example
* const customSerial = customType<{ data: number, notNull: true, default: true }>({
* dataType() {
* return 'serial';
* },
* });
*/
notNull?: boolean;
/**
* If your custom data type has default you can use `default: true`
*
* @example
* const customSerial = customType<{ data: number, notNull: true, default: true }>({
* dataType() {
* return 'serial';
* },
* });
*/
default?: boolean;
}
interface CustomTypeParams<T extends CustomTypeValues> {
/**
* Database data type string representation, that is used for migrations
* @example
* ```
* `jsonb`, `text`
* ```
*
* If database data type needs additional params you can use them from `config` param
* @example
* ```
* `varchar(256)`, `numeric(2,3)`
* ```
*
* To make `config` be of specific type please use config generic in {@link CustomTypeValues}
*
* @example
* Usage example
* ```
* dataType() {
* return 'boolean';
* },
* ```
* Or
* ```
* dataType(config) {
* return typeof config.length !== 'undefined' ? `varchar(${config.length})` : `varchar`;
* }
* ```
*/
dataType: (config: T['config'] | (Equal<T['configRequired'], true> extends true ? never : undefined)) => string;
/**
* Optional mapping function, that is used to transform inputs from desired to be used in code format to one suitable for driver
* @example
* For example, when using jsonb we need to map JS/TS object to string before writing to database
* ```
* toDriver(value: TData): string {
* return JSON.stringify(value);
* }
* ```
*/
toDriver?: (value: T['data']) => T['driverData'] | SQL;
/**
* Optional mapping function, that is used for transforming data returned by driver to desired column's output format
* @example
* For example, when using timestamp we need to map string Date representation to JS Date
* ```
* fromDriver(value: string): Date {
* return new Date(value);
* }
* ```
*
* It'll cause the returned data to change from:
* ```
* {
* customField: "2025-04-07 03:25:16.635";
* }
* ```
* to:
* ```
* {
* customField: new Date("2025-04-07 03:25:16.635");
* }
* ```
*/
fromDriver?: (value: 'driverOutput' extends keyof T ? T['driverOutput'] : T['driverData']) => T['data'];
/**
* Optional mapping function, that is used for transforming data returned by transofmed to JSON in database data to desired format
*
* Used by [relational queries](https://orm.drizzle.team/docs/rqb)
*
* Defaults to {@link fromDriver} function
* @example
* For example, when querying bigint column via [RQB](https://orm.drizzle.team/docs/rqb) or JSON functions, the result field will be returned as it's string representation, as opposed to bigint from regular query
* To handle that, we need a separate function to handle such field's mapping:
* ```
* fromJson(value: string): bigint {
* return BigInt(value);
* },
* ```
*
* It'll cause the returned data to change from:
* ```
* {
* customField: "5044565289845416380";
* }
* ```
* to:
* ```
* {
* customField: 5044565289845416380n;
* }
* ```
*/
fromJson?: (value: T['jsonData']) => T['data'];
/**
* Optional selection modifier function, that is used for modifying selection of column inside JSON functions
*
* Additional mapping that could be required for such scenarios can be handled using {@link fromJson} function
*
* Used by [relational queries](https://orm.drizzle.team/docs/rqb)
*
* Following types are being casted to text by default: `binary`, `varbinary`, `time`, `datetime`, `decimal`, `float`, `bigint`
* @example
* For example, when using bigint we need to cast field to text to preserve data integrity
* ```
* forJsonSelect(identifier: SQL, sql: SQLGenerator): SQL {
* return sql`cast(${identifier} as char)`
* },
* ```
*
* This will change query from:
* ```
* SELECT
* json_build_object('bigint', `t`.`bigint`)
* FROM
* (
* SELECT
* `table`.`custom_bigint` AS `bigint`
* FROM
* `table`
* ) AS `t`
* ```
* to:
* ```
* SELECT
* json_build_object('bigint', `t`.`bigint`)
* FROM
* (
* SELECT
* cast(`table`.`custom_bigint` as char) AS `bigint`
* FROM
* `table`
* ) AS `t`
* ```
*
* Returned by query object will change from:
* ```
* {
* bigint: 5044565289845416000; // Partial data loss due to direct conversion to JSON format
* }
* ```
* to:
* ```
* {
* bigint: "5044565289845416380"; // Data is preserved due to conversion of field to text before JSON-ification
* }
* ```
*/
forJsonSelect?: (identifier: SQL, sql: SQLGenerator) => SQL;
}