Skip to content

Composing schemas ​

Shapes are plain objects, and schemas can be derived from other schemas. Each transform below returns a new schema, and the inferred types follow.

Adding fields ​

There are two ways to add fields to an existing schema: spread its shape into a new one, or use extend. Both give the same fields, and in both a field with the same name as an existing one replaces it. extend also keeps the schema's annotations apart from examples, while a spread starts a new schema without any:

ts
import * as 
s
from '@sanity-labs/typed-schema'
const
Person
=
s
.
object
({
name
:
s
.
string
(),
email
:
s
.
string
(),
phone
:
s
.
optional
(
s
.
string
()),
}) const
Volunteer
=
s
.
object
({...
Person
.
shape
,
role
:
s
.
string
()})
type Volunteer =
s
.
Infer
<typeof
Volunteer
>
type Volunteer = {
    role: string;
    name: string;
    email: string;
    phone?: string | undefined;
}
const
Speaker
=
s
.
extend
(
Person
, {
talk
:
s
.
string
()})
type Speaker =
s
.
Infer
<typeof
Speaker
>
type Speaker = {
    name: string;
    email: string;
    talk: string;
    phone?: string | undefined;
}

Use extend when the new fields include a getter for a recursive schema: spreading a shape into the same object literal as a getter breaks type inference.

extend also takes a union of objects and adds the fields to every member. Each member keeps its own _type, so the result is still a union of typed objects. This is how the section types of a page builder can share fields:

ts
import * as 
s
from '@sanity-labs/typed-schema'
const
Hero
=
s
.
object
({
_type
:
s
.
literal
('hero'),
title
:
s
.
string
()})
const
Gallery
=
s
.
object
({
_type
:
s
.
literal
('gallery'),
columns
:
s
.
number
()})
const
Section
=
s
.
extend
(
s
.
union
([
Hero
,
Gallery
]), {
anchor
:
s
.
optional
(
s
.
string
()),
}) type Section =
s
.
Infer
<typeof
Section
>
type Section = {
    _type: "hero";
    title: string;
    anchor?: string | undefined;
} | {
    _type: "gallery";
    columns: number;
    anchor?: string | undefined;
}

A union with a reference, block, image or file member cannot be extended: those types have fixed shapes. _type cannot be replaced on a union either, since the members are told apart by it.

Removing fields with omit ​

ts
import * as 
s
from '@sanity-labs/typed-schema'
const
Person
=
s
.
object
({
name
:
s
.
string
(),
email
:
s
.
string
(),
phone
:
s
.
optional
(
s
.
string
()),
}) // leave out the phone number where people are listed publicly const
PublicPerson
=
s
.
omit
(
Person
, ['phone'])
type PublicPerson =
s
.
Infer
<typeof
PublicPerson
>
type PublicPerson = {
    name: string;
    email: string;
}

Keeping fields with pick ​

ts
import * as 
s
from '@sanity-labs/typed-schema'
const
Person
=
s
.
object
({
name
:
s
.
string
(),
email
:
s
.
string
(),
phone
:
s
.
optional
(
s
.
string
()),
}) const
Contact
=
s
.
pick
(
Person
, ['name', 'email'])
type Contact =
s
.
Infer
<typeof
Contact
>
type Contact = {
    name: string;
    email: string;
}

omit and pick on a document keep the system fields (_id, _rev, ...) even if you list them.

Changing the type name with rename ​

rename replaces a schema's _type literal:

ts
import * as 
s
from '@sanity-labs/typed-schema'
const
Writer
=
s
.
object
({
_type
:
s
.
literal
('writer'),
name
:
s
.
string
()})
type Writer =
s
.
Infer
<typeof
Writer
>
type Writer = {
    _type: "writer";
    name: string;
}
const
Author
=
s
.
rename
(
Writer
, 'author')
type Author =
s
.
Infer
<typeof
Author
>
type Author = {
    _type: "author";
    name: string;
}