Documentation Index
Fetch the complete documentation index at: https://docs.turso.tech/llms.txt
Use this file to discover all available pages before exploring further.
CREATE TYPE
Turso Extension: CREATE TYPE is a Turso-specific statement not available in standard SQLite. Custom types require STRICT tables. This feature is experimental and must be enabled before use.
Syntax
Description
A custom type wraps one of the four base storage types with user-defined logic. When a value is written to a column of a custom type, the ENCODE expression transforms it before storage. When a value is read, the DECODE expression transforms it back. This lets you store data in an efficient on-disk representation while presenting a different form to queries. Custom types work only with STRICT tables. Using a custom type name in a non-STRICT table has no effect.Clauses
IF NOT EXISTS
Suppresses the error that would occur if a type with the same name already exists. The existing type is left unchanged.BASE
Specifies the underlying SQLite storage class used to store values on disk. Every custom type must have a BASE clause.| Base Type | Description |
|---|---|
integer | Signed integer, stored in 1-8 bytes depending on magnitude |
real | 8-byte IEEE 754 floating-point number |
text | UTF-8 encoded string |
blob | Raw binary data |
ENCODE / DECODE
The ENCODE expression is evaluated whenever a value is written to a column of this type. The DECODE expression is evaluated whenever a value is read. Both expressions use the identifiervalue to refer to the input.
- ENCODE: Transforms the input value into the base storage form. Runs on INSERT, UPDATE, and CAST.
- DECODE: Transforms the stored value back into the presentation form. Runs on SELECT.
- NULL handling: NULL values bypass both ENCODE and DECODE. A NULL input produces a NULL output without evaluating the expression.
Parameters
Custom types can accept parameters that are available in the ENCODE and DECODE expressions. Parameters are declared as a parenthesized list after the type name. Each parameter has a name and an optional type annotation.value. Additional parameters are the arguments provided in the column type declaration, in order.
OPERATOR
The OPERATOR clause maps SQL operators to functions. This allows columns of a custom type to participate in comparisons, arithmetic, and ordering.| Component | Description |
|---|---|
'op' | The operator symbol as a string literal: '+', '-', '*', '/', '<', '=', etc. |
function-name | The name of a SQL function to call when this operator is used. Omit to use the base type’s native comparison. |
column + 1 are rewritten into function_name(column, 1).
Ordering with OPERATOR ’<’
The< operator is special: it controls how values of this type are sorted in ORDER BY, MIN, MAX, and CREATE INDEX.
OPERATOR '<'(no function name): Uses the base type’s native comparison for ordering. This is sufficient for types where the encoded form sorts correctly (e.g., text dates in ISO 8601 format).OPERATOR '<' my_compare: Usesmy_compare(a, b)as a custom comparator. The function must return a negative integer ifa < b, zero ifa = b, and a positive integer ifa > b.- No
<operator: The type cannot be used in ORDER BY or CREATE INDEX.
= operator, when defined, is also used to derive !=. The < operator is used to derive >, >=, and <= automatically by swapping arguments or negating the result.
DEFAULT
The DEFAULT clause sets a type-level default value. When a column of this type does not specify its own DEFAULT, this value is used.Validation with RAISE
Use CASE/WHEN with RAISE in the ENCODE expression to validate input at write time. If the condition fails, the statement is aborted with the specified error message.CAST Support
The CAST expression applies a custom type’s ENCODE logic:CHECK Constraints with Custom Types
In STRICT tables with custom types, CHECK constraints operate on the decoded (presentation) values. When comparing a column of a custom type against a literal, use CAST to encode the literal:Inspecting Types
PRAGMA list_types
Lists all available types, including built-in and user-defined:sqlite_turso_types Virtual Table
Thesqlite_turso_types virtual table provides the name and SQL definition of each type:
Using with ALTER TABLE
Custom types can be used when adding columns with ALTER TABLE ADD COLUMN:Built-in Types
Turso provides the following built-in custom types in STRICT tables:| Type | Base | Parameters | Description |
|---|---|---|---|
boolean | integer | — | Constrained to 0 or 1. Aliases: bool. |
smallint | integer | — | Integer constrained to -32768 through 32767. Aliases: int2. |
bigint | integer | — | Integer (no range constraint). Aliases: int8. |
varchar(N) | text | maxlen | Text with a maximum length of N characters. |
date | text | — | ISO 8601 date (YYYY-MM-DD), validated on insert. |
time | text | — | ISO 8601 time (HH:MM:SS), validated on insert. |
timestamp | text | — | ISO 8601 datetime, validated on insert. |
numeric(P,S) | blob | precision, scale | Fixed-point decimal with P total digits and S decimal places. Supports arithmetic operators. |
uuid | blob | — | UUID stored as 16-byte blob, displayed as string. Default: uuid4_str(). |
inet | text | — | Validated IP address (IPv4 or IPv6). |
bytea | blob | — | Binary data (PostgreSQL-compatible alias). |
json | text | — | Validated JSON text. |
jsonb | blob | — | JSON stored in binary format, returned as JSON text on read. |
The
uuid, json, and jsonb types require their respective extensions to be compiled in. They are available by default in standard Turso builds.Composite Types: STRUCT and UNION
In addition to encode/decode custom types, CREATE TYPE supports two composite type forms: STRUCT (named product type) and UNION (discriminated union / tagged variant). Both store data as blobs on disk and require STRICT tables.STRUCT
A STRUCT groups multiple named fields into a single column. Use dot notation to read and filter on individual fields:struct_pack()
Creates a struct value for INSERT and UPDATE. Arguments are positional, matching the field order in the type definition.struct_extract()
Function form of dot notation —struct_extract(col, 'field') is equivalent to col.field. Primarily useful in expression indexes, where dot notation cannot be used:
UNION
A UNION is a discriminated union (tagged variant). Each value carries exactly one of the declared variants, identified by a tag. Use dot notation to extract a variant’s value — it returns NULL when the active variant doesn’t match:union_value()
Creates a union value for INSERT and UPDATE. The first argument is the variant tag (a string literal), the second is the value. The tag is resolved against the target column’s union type.union_tag()
Returns the tag name of the active variant as text.union_extract()
Function form of dot notation —union_extract(col, 'variant') is equivalent to col.variant. Primarily useful in expression indexes:
NULL handling
NULL values propagate through all struct and union operations:Dot notation precedence
When a dot expressiona.b is ambiguous (e.g., a could be a table name or a column name), table references always take priority over struct field access. This follows DuckDB’s resolution rules.
a.b.c, the resolution order is: database.table.column, then table.column.field.
Inline types not supported
Inline STRUCT/UNION declarations in column definitions are not supported. Always use CREATE TYPE first:Array Types
Array columns store ordered collections of values. Unlike STRUCT and UNION, arrays don’t require an explicit CREATE TYPE — declare them by appending[] to any base type name in a STRICT table column definition:
INTEGER[] column rejects values that cannot be stored as integers. Arrays are stored internally as compact record-format BLOBs and displayed as JSON arrays on output.
For the full set of array functions (array_agg, array_append, array_contains, array_slice, etc.), operators (@>, &&, ||), and subscript syntax, see Array Functions.
Restrictions
- STRICT tables only: Custom type names are ignored in non-STRICT tables. The column uses standard type affinity rules instead.
- Cannot drop while in use: A type cannot be dropped with DROP TYPE while any table has a column of that type.
If a non-STRICT table was created with a type name that did not exist at the time (e.g.,
CREATE TABLE t(x mytype)), and a custom type with that name is created afterwards (CREATE TYPE mytype ...), the type’s ENCODE/DECODE logic will not be retroactively applied to the existing table. This is not a bug: non-STRICT tables treat all type names as plain affinity hints regardless of whether a matching custom type exists. STRICT tables prevent this scenario because they reject unknown type names at CREATE TABLE time.- No subqueries in expressions: The ENCODE, DECODE, and DEFAULT expressions cannot contain subqueries, aggregate functions, or window functions.
- No indexes on STRUCT/UNION columns: CREATE INDEX on a STRUCT or UNION column is not supported.
Examples
Identity Type (Passthrough)
A minimal type that stores and retrieves values without transformation:Monetary Values as Cents
Store dollar amounts as integer cents for exact arithmetic, present them as the original value:JSON Validation
Validate that inserted values are valid JSON:Unsigned Integer with Operators
A non-negative integer type with arithmetic and comparison operators:Fixed-Point Decimal
Use the built-innumeric type for precise decimal arithmetic:
See Also
- DROP TYPE for removing custom types
- CREATE DOMAIN for defining constrained type aliases without ENCODE/DECODE logic
- Data Types for an overview of the type system and type affinity
- Array Types for native array columns (
INTEGER[],TEXT[], etc.) - Array Functions for array construction, manipulation, and operators
- CREATE TABLE for STRICT table definitions