Skip to content

Custom creators ​

The built-in creators (object(), array(), block(), ...) are ordinary functions that return schemas. When the same structure appears in several places with different contents, write a creator of your own the same way.

Example: a table creator ​

A table is a list of rows, each holding a list of cells. Since Sanity does not allow arrays of arrays, each row is an object. Like block(), table() takes an options object: an optional _type that defaults to table, and the schema for a cell, which can be any object or primitive schema, but not another array:

ts
import * as 
s
from '@sanity-labs/typed-schema'
function
table
<
CellType
extends
|
s
.
ObjectLikeSchema
|
s
.
ObjectUnionSchema
|
s
.
PrimitiveSchema
|
s
.
PrimitiveUnionSchema
,
TypeName
extends
s
.
LiteralSchema
<string> =
s
.
LiteralSchema
<'table'>,
>(
options
: {
_type
?:
TypeName
;
cell
:
CellType
}) {
return
s
.
object
({
_type
: (
options
.
_type
??
s
.
literal
('table')) as
TypeName
,
rows
:
s
.
array
(
s
.
object
({
cells
:
s
.
array
(
options
.
cell
)})),
}) } const
Session
=
s
.
object
({
_type
:
s
.
literal
('session'),
title
:
s
.
string
(),
room
:
s
.
string
(),
}) // sessions by time slot and track const
Timetable
=
table
({
_type
:
s
.
literal
('timetable'),
cell
:
Session
})
type Timetable =
s
.
Infer
<typeof
Timetable
>
type Timetable = {
    _type: "timetable";
    rows: {
        _key: string;
        cells: {
            _type: "session";
            _key: string;
            title: string;
            room: string;
        }[];
    }[];
}
const
NumberGrid
=
table
({
_type
:
s
.
literal
('numberGrid'),
cell
:
s
.
number
()})
type NumberGrid =
s
.
Infer
<typeof
NumberGrid
>
type NumberGrid = {
    _type: "numberGrid";
    rows: {
        _key: string;
        cells: number[];
    }[];
}

Build on other creators ​

A creator can call another one to narrow what it accepts. objectTable() is a table() that only accepts object cells. emailTable() fixes both the cell and the type name, so callers pass nothing:

ts
// table() is the creator from the example above

function 
objectTable
<
CellType
extends
s
.
TypedObjectSchema
|
s
.
ObjectUnionSchema
,
TypeName
extends
s
.
LiteralSchema
<string> =
s
.
LiteralSchema
<'table'>,
>(
options
: {
_type
?:
TypeName
;
cell
:
CellType
}) {
return
table
(
options
)
} function
emailTable
() {
return
table
({
_type
:
s
.
literal
('emailTable'),
cell
:
s
.
string
()})
} const
Measurement
=
s
.
object
({
_type
:
s
.
literal
('measurement'),
value
:
s
.
number
(),
unit
:
s
.
string
(),
}) const
Note
=
s
.
object
({
_type
:
s
.
literal
('note'),
text
:
s
.
string
()})
const
MeasurementTable
=
objectTable
({
_type
:
s
.
literal
('measurementTable'),
cell
:
Measurement
,
}) type MeasurementTable =
s
.
Infer
<typeof
MeasurementTable
>
type MeasurementTable = {
    _type: "measurementTable";
    rows: {
        _key: string;
        cells: {
            _type: "measurement";
            _key: string;
            value: number;
            unit: string;
        }[];
    }[];
}
// measurements, with notes such as "not measured" in some cells const
ResultsTable
=
objectTable
({
_type
:
s
.
literal
('resultsTable'),
cell
:
s
.
union
([
Measurement
,
Note
]),
}) type ResultsTable =
s
.
Infer
<typeof
ResultsTable
>
type ResultsTable = {
    _type: "resultsTable";
    rows: {
        _key: string;
        cells: ({
            _type: "measurement";
            _key: string;
            value: number;
            unit: string;
        } | {
            _type: "note";
            _key: string;
            text: string;
        })[];
    }[];
}
const
Contacts
=
emailTable
()
type Contacts =
s
.
Infer
<typeof
Contacts
>
type Contacts = {
    _type: "emailTable";
    rows: {
        _key: string;
        cells: string[];
    }[];
}

Constrain options with schema types ​

Give each schema option a type parameter constrained by one of the library's schema types, such as TypedObjectSchema (an object with a _type literal), ObjectUnionSchema (a union of those) or PrimitiveSchema (a string, number, boolean, date, date-time or literal). The inferred type then follows whatever schema the caller passes, and passing the wrong kind of schema is a type error at the call site:

ts
// table() and objectTable() are the creators from the examples above

const 
Tags
=
objectTable
({
cell
:
s
.
string
()})

Name each use ​

Every call to a creator returns a new schema, so two tables with the default _type are two declarations of the type name table, which verifyUniqueTypeNames rejects. Pass a _type to each table that holds different cells:

ts
// table() and objectTable() are the creators from the examples above

const 
Measurement
=
s
.
object
({
_type
:
s
.
literal
('measurement'),
value
:
s
.
number
(),
unit
:
s
.
string
(),
}) const
Note
=
s
.
object
({
_type
:
s
.
literal
('note'),
text
:
s
.
string
()})
const
Experiment
=
s
.
verifyUniqueTypeNames
(
s
.
document
({
_type
:
s
.
literal
('experiment'),
measurements
:
objectTable
({
_type
:
s
.
literal
('measurementTable'),
cell
:
Measurement
,
}),
results
:
objectTable
({
_type
:
s
.
literal
('resultsTable'),
cell
:
s
.
union
([
Measurement
,
Note
]),
}), }), ) type
Experiment
=
s
.
Infer
<typeof
Experiment
>

To use the same table in more than one place, call the creator once and reuse the schema it returns.