sanity
sanity copied to clipboard
TypeScript types for schema
We are writing our site in typescript, using Gatsby and React. It would be great if the Sanity schema could be compiled to typescript interfaces, so that we could leverage static type safety during development.
I imagine generating this with a sanity
cli command.
Given the translation layers between Sanity, GraphQL and TypeScript, I imagine this is is a subtle problem. If there's any more info I can provide to be helpful, let me know.
We wrote something ourselves, until there are official type definitions: https://gist.github.com/barbogast/4bea3ad77272fafe0af3d4f70446d037
If you're interested we could create a repository and a NPM package. (Not sure how exactly to do this with TS, though).
we could do a PR, or we could submit through https://github.com/DefinitelyTyped/DefinitelyTyped
Thanks for the responses. To clarify, the request here is not typescript types for writing out schemas, but rather typescript types for the types implied by the schema.
For example, I would want something like this for the Person
example documented here:
namespace sanity {
interface IPerson {
// Name.
name: string;
}
}
This would allow type-checking for React components that use data from Sanity as props.
Oh, okay. Do you want to use it for your client or within the studio? If it is for clients you probably want to have types for queries, not the schema. With both groq and graphql it is possible to reshape how the data actually arrives on the client. So the type definitions need to follow the query, not the schema (except if your queries always return the exact shape of the schema).
For graphql there might already be solutions out there (https://github.com/dotansimha/graphql-code-generator for example).
Oh, okay. Do you want to use it for your client or within the studio? If it is for clients you probably want to have types for queries, not the schema. With both groq and graphql it is possible to reshape how the data actually arrives on the client. So the type definitions need to follow the query, not the schema (except if your queries always return the exact shape of the schema).
For graphql there might already be solutions out there (https://github.com/dotansimha/graphql-code-generator for example).
Are there plans to add something similar for GROQ queries? It would be very nice to have end-to-end type safety without having to hand type everything.
For those using the GraphQL api and queries, its possible to use graphql code generator to create the types for you. Here is an example of the config file. This will grab the schema from the sanity api endpoint, look through your apps files for graphql queries and create a ts file with all the types for you.
#codegen.yaml
overwrite: true
schema: https://<projectId>.api.sanity.io/v1/graphql/<dataset>/<tag>
documents: "src/**/*.{ts,tsx,gql,graphql}"
generates:
src/types/generated/graphcms-schema.ts:
plugins:
- typescript
- typescript-operations
Is there official types for the Sanity document/schemas yet? I can't seem to figure out what type to use when defining a schema. I've tried something random like this but this not the right type.
import { SchemaType } from '@sanity/types';
const Page: SchemaType = {
name: 'page',
type: 'document',
title: 'Page',
fields: [
{
name: 'title',
type: 'string',
title: 'Title',
},
{
name: 'description',
type: 'string',
title: 'Description',
},
],
};
export default Page;
+1
SchemaType
is a close call but it's still not exactly what the schema is supposed to look like. @bjoerge @rexxars Would you be able to show us a direction?
I am building on top of @barbogast schema. Should we create a collaborative repo out of it?
import * as React from 'react';
import { ElementType, ReactNode } from 'react';
type Meta = {
parent: { [key: string]: any };
path: string[];
document: { [key: string]: any };
};
type CustomRuleCallback = (field: any, meta: Meta) => true | string | Promise<true | string>;
export type RuleType = {
required: () => RuleType;
custom: (cb: CustomRuleCallback) => RuleType;
min: (min: number) => RuleType;
max: (max: number) => RuleType;
length: (exactLength: number) => RuleType;
greaterThan: (gt: number) => RuleType;
uri: (options: { scheme: string[] }) => RuleType;
integer: () => RuleType;
precision: (limit: number) => RuleType;
};
type Validation = (rule: RuleType) => RuleType | RuleType[];
export type CommonFieldProps = {
title?: string;
fieldset?: string;
validation?: Validation;
description?: string;
hidden?: boolean;
readOnly?: boolean;
defaultValue?: any;
inputComponent?: ElementType;
};
export type StringField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'string';
options?:
| {
list: { title: string; value: string }[] | string[];
layout?: string;
}
| never;
};
export type NumberField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'number';
options?: {
list: { title: string; value: string }[] | string[];
};
};
export type TextField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'text';
rows?: number;
};
export type BooleanField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'boolean';
options?: {
layout?: 'switch' | 'checkbox';
};
};
export type DateField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'date';
options?: {
dateFormat?: string;
};
};
export type SlugField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'slug';
options?: {
source?: string;
};
};
export type UrlField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'url';
};
export type BlockField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'block';
styles?: Array<{
title: string;
value: string;
blockEditor?: {
render: ElementType;
};
icon?: ElementType;
}>;
lists?: Array<{
title: string;
value: string;
}>;
marks?: {
annotations?: ArrayOf[];
decorators?: Array<{
title: string;
value: string;
icon?: ElementType;
}>;
};
of?: ArrayOf[];
icon?: ElementType;
};
type ArrayOf = ObjectField | ReferenceField | ImageField | { type: string } | BlockField;
export type ArrayField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'array';
of: ArrayOf[];
};
type FilterFunctionResult = { filter: string; filterParams?: string };
type FilterFunction = (args: {
document: { [key: string]: any };
parentPath: string[];
parent: Record<string, unknown>[];
}) => FilterFunctionResult;
type ReferenceField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'reference';
to: { type: string }[];
options?: {
filter: string | FilterFunction;
filterParams?: { [key: string]: string };
};
};
type ImageField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'image';
options?: {
hotspot?: boolean;
};
};
type FileField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'file';
};
export type CustomField<Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'money' | 'color' | 'icon' | 'iconPicker' | 'blockContent' | 'metadata';
options?: Record<string, any>;
};
export type FieldCollection<T extends string> = Array<Field<T>>;
export type Field<Name extends string = string> =
| StringField<Name>
| NumberField<Name>
| TextField<Name>
| BooleanField<Name>
| DateField<Name>
| SlugField<Name>
| UrlField<Name>
| ArrayField<Name>
| ReferenceField<Name>
| ImageField<Name>
| FileField<Name>
| ObjectField<any, Name>
| BlockField<Name>
| CustomField<Name>;
type Preview = {
select?: { [key: string]: string };
prepare?: (selection: {
[key: string]: any;
}) => {
title?: ReactNode;
subtitle?: ReactNode;
media?: ReactNode;
};
component?: React.VFC;
};
type Fieldset = {
name: string;
title: string;
options?: { collapsible: boolean; collapsed?: boolean; columns?: number };
};
export type ObjectField<Schema extends any = any, Name extends string = string> = CommonFieldProps & {
name: Name;
type: 'object';
title?: string;
fields: FieldCollection<keyof Schema>;
validation?: Validation;
preview?: Preview;
fieldsets?: Fieldset[];
description?: string;
options?: { collapsible?: boolean; collapsed?: boolean };
};
export type Document<T extends Record<string, any>> = {
type: 'document';
name: string;
fields: FieldCollection<keyof T>;
title?: string;
validation?: Validation;
preview?: Preview;
fieldsets?: Fieldset[];
initialValue?: { [key: string]: any };
orderings?: {
name: string;
title: string;
by: { field: string; direction: string }[];
}[];
};
export type PreviewProps<T extends Record<string, any>> = {
value: T;
};
export type Body2TextProps = { children: React.FunctionComponent<any> };
import { Substance } from '@fernarzt/cms-types';
import { Document, ObjectField } from '../../../types/createSchema';
export const SubstanceDocument: Document<Substance> = {
type: 'document',
name: 'Substance',
title: 'Substances',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
validation: (rule) => rule.required(),
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
},
validation: (rule) => rule.required(),
},
{
name: 'codes',
title: 'Codes',
type: 'object',
fields: [
{
name: 'act',
title: 'ACT Code',
type: 'object',
fields: [
{
name: 'code',
title: 'Code',
type: 'string',
},
{
name: 'title',
title: 'Title',
type: 'string',
},
],
},
],
} as ObjectField<Substance['codes'], 'codes'>,
],
preview: {
select: {
title: 'codes.act.code',
subtitle: 'title',
},
},
};
@hypeJunction @bjoerge @rexxars shall we make it as a PR?
@alvis We could fork, use a shared upstream, create a branch, and make a PR. We can work on the PR until it's universal enough. It's a bit hard to get all the type hints right.
There is also ricokahler/sanity-codegen.
BUMP
whats the to go to in this space?
@ricokahler 's plugin? was there any movement in the discussions above?
I would LOVE this
https://sanity-codegen-dev.vercel.app/ saved my ass
import picoSanity from "picosanity"
type Query = (string | null)[]
export default picoSanity({
projectId: process.env.SANITY_PROJECTID,
dataset: process.env.SANITY_DATASET,
apiVersion: '2022-02-22',
useCdn: false,
}).fetch<Query>(`*[_type=='todo'] | order(_createdAt desc).title`)
edit: this looks hot 🔥🔥🔥 https://github.com/ricokahler/sanity-codegen/tree/alpha/packages/client
I'm not sure how to put this in DefinitelyTyped (due to sanity parts having nonstandard import paths) but here's my types to share. It does the basics of all types, everything I haven't (or won't) get to is all typed unknown
:
type MaybeArray<T> = T | T[];
type MaybePromise<T> = T | Promise<T>;
/** @link https://www.sanity.io/docs/validation */
interface Rule<ExtendingRule, Value> {
custom: (
validator: (value: Value, context: unknown) => MaybePromise<false | string>
) => ExtendingRule;
error: (message: string) => ExtendingRule;
required: () => ExtendingRule;
valueOfField: (field: string) => unknown;
warning: (message: string) => ExtendingRule;
}
interface LengthRule<ExtendingRule> {
length: (exactLength: number) => ExtendingRule;
}
interface MinMaxRule<ExtendingRule> {
max: (maxValue: number) => ExtendingRule;
min: (minLength: number) => ExtendingRule;
}
interface StringContentRule<ExtendingRule> {
lowercase: () => ExtendingRule;
regex: (
regex: RegExp,
options?: {
invert?: boolean;
name?: string;
}
) => ExtendingRule;
uppercase: () => ExtendingRule;
}
interface ListItem<Value> {
title: string;
value: Value;
}
type ListItems<Value> = Value[] | ListItem<Value>[];
type ListOptions<Value> =
| {
layout?: "dropdown";
list?: ListItems<Value>;
}
| {
direction?: "horizontal" | "vertical";
layout: "radio";
list?: ListItems<Value>;
};
interface NamedDef<Name extends string> {
name: Name;
title?: string;
}
/** @link https://www.sanity.io/docs/initial-value-templates */
interface WithInitialValue<Value> {
initialValue?: Value | (() => Promise<Value>);
}
/** @link https://www.sanity.io/docs/conditional-fields */
type ConditionalField<Value> =
| boolean
| ((context: {
currentUser: {
email: string;
id: string;
name: string;
profileImage: string;
roles: {
description?: string;
name: string;
title?: string;
}[];
};
document?: unknown;
parent?: unknown;
value: Value;
}) => boolean);
/** @link https://www.sanity.io/docs/schema-types */
interface FieldDef<Name extends string, Rule, Value>
extends NamedDef<Name>,
WithInitialValue<Value> {
/** @link https://github.com/ricokahler/sanity-codegen/tree/alpha#schema-codegen-options */
codegen?: { required: boolean };
description?: string;
hidden?: ConditionalField<Value>;
readonly?: ConditionalField<Value>;
/** @link https://www.sanity.io/docs/validation */
validation?: (rule: Rule) => MaybeArray<Rule>;
}
/** @link https://www.sanity.io/docs/block-type#validation */
interface BlockRule extends Rule<BlockRule, unknown> {}
/** @link https://www.sanity.io/docs/block-type */
interface BlockFieldDef<Name extends string>
extends FieldDef<Name, BlockRule, unknown> {
icon?: (...args: unknown[]) => unknown;
lists?: ListItem<unknown>[];
marks?: unknown;
of?: unknown[];
options?: { spellCheck?: boolean };
styles?: ListItem<unknown>[];
}
/** @link https://www.sanity.io/docs/boolean-type#validation */
interface BooleanRule extends Rule<BooleanRule, boolean> {}
/** @link https://www.sanity.io/docs/boolean-type */
interface BooleanFieldDef<Name extends string>
extends FieldDef<Name, BooleanRule, boolean> {
options?: { layout?: "checkbox" | "switch" };
type: "boolean";
}
/** @link https://www.sanity.io/docs/date-type#validation */
interface DateRule extends Rule<DateRule, string> {}
/** @link https://www.sanity.io/docs/date-type */
interface DateFieldDef<Name extends string>
extends FieldDef<Name, DateRule, string> {
options?: {
calendarTodayLabel?: string;
dateFormat?: string;
};
}
/** @link https://www.sanity.io/docs/datetime-type#validation */
interface DatetimeRule
extends Rule<DatetimeRule, string>,
MinMaxRule<DatetimeRule> {}
/** @link https://www.sanity.io/docs/datetime-type */
interface DatetimeFieldDef<Name extends string>
extends FieldDef<Name, DatetimeRule, string> {
options?: {
calendarTodayLabel?: string;
dateFormat?: string;
timeFormat?: string;
timeStep?: number;
};
}
interface GeopointValue {
alt: number;
lat: number;
lng: number;
}
/** @link https://www.sanity.io/docs/geopoint-type#validation */
interface GeopointRule extends Rule<GeopointRule, GeopointValue> {}
/** @link https://www.sanity.io/docs/geopoint-type */
interface GeopointFieldDef<Name extends string>
extends FieldDef<Name, GeopointRule, GeopointValue> {
options?: {
calendarTodayLabel?: string;
dateFormat?: string;
timeFormat?: string;
timeStep?: number;
};
}
/** @link https://www.sanity.io/docs/number-type#validation */
interface NumberRule extends Rule<NumberRule, number>, MinMaxRule<NumberRule> {
greaterThan: (limit: number) => NumberRule;
integer: () => NumberRule;
lessThan: (limit: number) => NumberRule;
negative: () => NumberRule;
positive: () => NumberRule;
precision: (limit: number) => NumberRule;
}
/** @link https://www.sanity.io/docs/number-type */
interface NumberFieldDef<Name extends string>
extends FieldDef<Name, NumberRule, number> {
options?: ListOptions<number>;
type: "number";
}
interface ReferenceValue {
_ref: string;
_type: "reference";
}
/** @link https://www.sanity.io/docs/reference-type#validation */
interface ReferenceRule extends Rule<ReferenceRule, ReferenceValue> {}
/** @link https://www.sanity.io/docs/reference-type */
interface ReferenceFieldDef<DocumentNames extends string, Name extends string>
extends FieldDef<Name, ReferenceRule, ReferenceValue> {
options?: {
disableNew?: boolean;
} & ({ filter?: string; filterParams?: object } & {
filter?: (context: {
document: unknown;
parent: unknown;
parentPath: string;
}) => MaybePromise<{
filter: string;
params: unknown;
}>;
});
to: { type: DocumentNames }[];
type: "reference";
weak?: boolean;
}
/** @link https://www.sanity.io/docs/slug-type#validation */
interface SlugRule extends Rule<SlugRule, string> {}
/** @link https://www.sanity.io/docs/slug-typen */
interface SlugDef<Name extends string>
extends FieldDef<Name, SlugRule, string> {
options?: {
isUnique?: (value: string, options: unknown) => MaybePromise<boolean>;
maxLength?: number;
slugify?: (value: string, type: unknown) => MaybePromise<string>;
source?:
| string
| ((context: {
doc: unknown;
options: {
parent: unknown;
parentPath: string;
};
}) => string);
};
type: "slug";
}
/** @link https://www.sanity.io/docs/string-type#validation */
interface StringRule
extends Rule<StringRule, string>,
LengthRule<StringRule>,
MinMaxRule<StringRule>,
StringContentRule<StringRule> {}
/** @link https://www.sanity.io/docs/string-type */
interface StringFieldDef<Name extends string>
extends FieldDef<Name, StringRule, string> {
options?: ListOptions<string>;
type: "string";
}
/** @link https://www.sanity.io/docs/text-type#validation */
interface TextRule
extends Rule<TextRule, string>,
LengthRule<TextRule>,
MinMaxRule<TextRule>,
StringContentRule<TextRule> {}
/** @link https://www.sanity.io/docs/text-type */
interface TextFieldDef<Name extends string>
extends FieldDef<Name, TextRule, string> {
type: "text";
}
/** @link https://www.sanity.io/docs/url-type#validation */
interface URLRule extends Rule<URLRule, string> {
uri: (options: {
allowRelative?: boolean;
relativeOnly?: boolean;
scheme?: string[];
}) => URLRule;
}
/** @link https://www.sanity.io/docs/url-type */
interface URLFieldDef<Name extends string>
extends FieldDef<Name, URLRule, string> {
type: "url";
}
type PrimitiveFieldDef<Name extends string> =
| BooleanFieldDef<Name>
| DateFieldDef<Name>
| DatetimeFieldDef<Name>
| NumberFieldDef<Name>
| StringFieldDef<Name>
| TextFieldDef<Name>
| URLFieldDef<Name>;
type NonPrimitiveFieldDef<
DocumentNames extends string,
ObjectNames extends string,
Name extends string,
FieldNames extends string
> =
/* eslint-disable no-use-before-define -- Circular dependency */
| FileFieldDef<DocumentNames, ObjectNames, Name, FieldNames>
| ImageFieldDef<DocumentNames, ObjectNames, Name, FieldNames>
| ObjectFieldDef<DocumentNames, ObjectNames, Name, FieldNames, string, string>
/* eslint-enable no-use-before-define */
| GeopointFieldDef<Name>
| ReferenceFieldDef<DocumentNames, Name>
| SlugDef<Name>;
/** @link https://www.sanity.io/docs/array-type#validation */
interface ArrayRule
extends Rule<ArrayRule, unknown[]>,
LengthRule<ArrayRule>,
MinMaxRule<ArrayRule> {
unique: () => ArrayRule;
}
/** @link https://www.sanity.io/docs/array-type */
interface ArrayFieldDef<
DocumentNames extends string,
ObjectNames extends string,
Name extends string
> extends FieldDef<Name, ArrayRule, unknown[]> {
of:
| Omit<BlockFieldDef<never>, "name">[]
| Omit<PrimitiveFieldDef<never>, "name">[]
| (
| Omit<
NonPrimitiveFieldDef<DocumentNames, ObjectNames, never, string>,
"name"
>
| Omit<ReferenceFieldDef<DocumentNames, never>, "name">
| {
title?: string;
type: ObjectNames;
}
)[];
options?: {
editModal?: "dialog" | "fullscreen";
layout?: "grid" | "tags";
list?: ListItem<string>[];
sortable?: boolean;
};
type: "array";
}
type FieldType<
DocumentNames extends string,
ObjectNames extends string,
Name extends string,
FieldNames extends string
> =
| ArrayFieldDef<DocumentNames, ObjectNames, Name>
| NonPrimitiveFieldDef<DocumentNames, ObjectNames, Name, FieldNames>
| PrimitiveFieldDef<Name>;
type FileValue<FieldNames extends string> = Record<
Exclude<FieldNames, "_type" | "asset">,
string
> & {
_type: "file";
asset: ReferenceValue;
};
/** @link https://www.sanity.io/docs/arfileray-type#validation */
interface FileRule<FieldNames extends string>
extends Rule<FileRule<FieldNames>, FileValue<FieldNames>> {}
/** @link https://www.sanity.io/docs/file-type */
interface FileFieldDef<
DocumentNames extends string,
ObjectNames extends string,
Name extends string,
FieldNames extends string
> extends FieldDef<Name, FileRule<FieldNames>, FileValue<FieldNames>> {
fields?: FieldType<DocumentNames, ObjectNames, FieldNames, string>[];
options?: {
/** @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers */
accept?: string;
/** @link https://www.sanity.io/docs/custom-asset-sources */
sources?: unknown[];
storeOriginalFilename?: boolean;
};
type: "file";
}
type ImageValue<FieldNames extends string> = Record<
Exclude<FieldNames, "_type" | "asset" | "crop" | "hotspot">,
string
> & {
_type: "image";
asset: ReferenceValue;
crop: {
bottom: number;
left: number;
right: number;
top: number;
};
hotspot: {
height: number;
width: number;
x: number;
y: number;
};
};
/** @link https://www.sanity.io/docs/image-type#validation */
interface ImageRule<FieldNames extends string>
extends Rule<ImageRule<FieldNames>, ImageValue<FieldNames>> {}
/** @link https://www.sanity.io/docs/image-type */
interface ImageFieldDef<
DocumentNames extends string,
ObjectNames extends string,
Name extends string,
FieldNames extends string
> extends FieldDef<Name, ImageRule<FieldNames>, unknown> {
fields?: (FieldType<DocumentNames, ObjectNames, FieldNames, string> & {
isHighlighted?: boolean;
})[];
options?: {
/** @link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers */
accept?: string;
hotspot?: boolean;
/** @link https://www.sanity.io/docs/image-metadata */
metadata?: string[];
/** @link https://www.sanity.io/docs/custom-asset-sources */
sources?: unknown[];
storeOriginalFilename?: boolean;
};
type: "image";
}
interface ObjectLikeDef<
DocumentNames extends string,
ObjectNames extends string,
FieldNames extends string,
FieldSetNames extends string,
SelectionNames extends string,
GroupNames extends string
> {
fields: (FieldType<DocumentNames, ObjectNames, FieldNames, string> & {
/** @link https://www.sanity.io/docs/object-type#AbjN0ykp */
fieldset?: FieldSetNames;
/** @link https://www.sanity.io/docs/field-groups */
group?: MaybeArray<GroupNames>;
})[];
/** @link https://www.sanity.io/docs/object-type#AbjN0ykp */
fieldsets?: {
name: FieldSetNames;
title: string;
}[];
/** @link https://www.sanity.io/docs/previews-list-views */
preview?:
| {
select: {
media?: string | unknown;
subtitle?: FieldNames;
title?: FieldNames;
};
}
| {
component?: (props: {
[name in SelectionNames]: unknown;
}) => unknown;
prepare: (selection: {
[name in SelectionNames]: unknown;
}) => {
media?: string | unknown;
subtitle?: FieldNames;
title?: FieldNames;
};
select: {
[name in SelectionNames]: FieldNames;
};
};
}
/** @link https://www.sanity.io/docs/object-type#validation */
interface ObjectRule extends Rule<ObjectRule, unknown> {}
/** @link https://www.sanity.io/docs/object-type */
interface ObjectFieldDef<
DocumentNames extends string,
ObjectNames extends string,
Name extends string,
FieldNames extends string,
FieldSetNames extends string,
SelectionNames extends string
> extends FieldDef<Name, ObjectRule, unknown>,
ObjectLikeDef<
DocumentNames,
ObjectNames,
FieldNames,
FieldSetNames,
SelectionNames,
never
> {
inputComponent?: unknown;
type: "object";
}
type ObjectDef<
// DocumentNames & ObjectNames reversed!!! Mostly for convenience when defining types
ObjectNames extends string,
DocumentNames extends string = never,
FieldNames extends string = string,
FieldSetNames extends string = string,
SelectionNames extends string = string
> = ObjectFieldDef<
DocumentNames,
ObjectNames,
ObjectNames,
FieldNames,
FieldSetNames,
SelectionNames
>;
/** @link https://www.sanity.io/docs/document-type */
interface DocumentDef<
DocumentNames extends string,
ObjectNames extends string = never,
FieldNames extends string = string,
FieldSetNames extends string = string,
SelectionNames extends string = string,
GroupNames extends string = string
> extends NamedDef<DocumentNames>,
WithInitialValue<unknown>,
ObjectLikeDef<
DocumentNames,
ObjectNames,
FieldNames,
FieldSetNames,
SelectionNames,
GroupNames
> {
/** @link https://www.sanity.io/docs/field-groups */
groups?: (NamedDef<string> & {
default?: boolean;
hidden?: ConditionalField<unknown>;
icon?: unknown;
})[];
liveEdit?: boolean;
/** @link https://www.sanity.io/docs/sort-orders */
orderings?: (NamedDef<string> & {
by: {
direction: "asc" | "desc";
field: FieldNames;
}[];
})[];
type: "document";
}
type SchemaType<
DocumentNames extends string = any,
ObjectNames extends string = any,
FieldNames extends string = string,
FieldSetNames extends string = string,
SelectionNames extends string = string,
GroupNames extends string = string
> =
| DocumentDef<
DocumentNames,
ObjectNames,
FieldNames,
FieldSetNames,
SelectionNames,
GroupNames
>
| ObjectDef<
ObjectNames,
DocumentNames,
FieldNames,
FieldSetNames,
SelectionNames
>;
declare module "@sanity/base" {
export const ObjectDef;
export const DocumentDef;
export const SchemaType;
}
declare module "part:@sanity/base/schema-creator" {
import type { Schema } from "@sanity/schema/dist/dts/legacy/Schema";
const createSchema: <
DocumentNames extends string,
ObjectNames extends string
>(schemaDef: {
name: string;
types: SchemaType<DocumentNames, ObjectNames>[];
}) => Schema;
export default createSchema;
export const ObjectDef;
export const DocumentDef;
export const SchemaType;
}
declare module "all:part:@sanity/base/schema-type" {
const schemaTypes: SchemaType[];
export default schemaTypes;
}
Thanks a lot for sharing this! That could become quite handy! :pray:
I’d like to include these in DefinitelyTyped but I’m not sure how. If anyone knows the answer to my issue, please help!
https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/59497
Sorry to pollute this thread and the ironic tone, but ... IMO Sanity in 2022 should have 100% TypeScript support, hell, all the init/sample projects should be scaffolded in TS
What is the point of GROQ Graphql, etc if everything in sanity (by default) is in plain JS?

Does anyone want to write any schema or plugin without TS today? Coming from other CMEs and framework, I am baffled by those schema files with 0 autocomplete or types
Edit:
The official docs have so little on that topic: https://www.sanity.io/docs/using-typescript-in-sanity-studio
Sorry to pollute this thread and the ironic tone, but ... IMO Sanity in 2022 should have 100% TypeScript support, hell, all the init/sample projects should be scaffolded in TS
What is the point of GROQ Graphql, etc if everything in sanity (by default) is in plain JS?
![]()
Does anyone want to write any schema or plugin without TS today? Coming from other CMEs and framework, I am baffled by those schema files with 0 autocomplete or types
Edit:
The official docs have so little on that topic: https://www.sanity.io/docs/using-typescript-in-sanity-studio
100% this
my mind was so boggled by how they took all the time for TS yet didnt pass any of that on
still 🤯🤯
I'm attempting to get some types pushed into Definitely Typed so there's a starting point for people to iterate on, feel free to push on it https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60376
Has there been any update on this? Having end-to-end typings is pretty important IMHO. With sanity-codegen we can type the frontend based on the schema but without proper schema types, the bases for those typings is non-typed hand-written JS, certainly not an ideal situation.
Also, does anyone have any schema examples using the current loose types? Either builtin or contributed. Thanks!
Just a short update to this thread! We just announced the Developer Preview of Sanity Studio v3 with new APIs that are fully typed, including the Schema API.
This might not be exactly what you want, so I'd also check out @saiichihashimoto's sanity-typed-schema-builder that he just posted in Awesome Sanity Studio v3
sanity-typed-schema-builder is meant to type the frontend document types off of the schemas. Codegen was really bothering me so I wrote this library.
@saiichihashimoto
I'd really like to use sanity-typed-schema-builder until v3 is released, but I'm having issues with it ~and it seems like the GitHub repo has been removed despite it being updated on NPM recently. So I'm not sure where else to ask for help with it~.
edit: see https://github.com/saiichihashimoto/sanity-typed-schema-builder/issues/113
Hey @han-tyumi! I just made the repo public, can you add an issue into there? It being private was an accident.
Not a full solution, but @han-tyumi found the issue in https://github.com/saiichihashimoto/sanity-typed-schema-builder/issues/113
With Sanity studio v3 we're now shipping defineType()
methods to help with this.
What's the latest on this? Is there first class support for e2e type safety?