Gathering detailed insights and metrics for @sanity-typed/types
Gathering detailed insights and metrics for @sanity-typed/types
Gathering detailed insights and metrics for @sanity-typed/types
Gathering detailed insights and metrics for @sanity-typed/types
@sanity-typed/client
@sanity/client with typed GROQ Results
@sanity-typed/groq
Infer GROQ Result Types from GROQ strings
@sanity-typed/groq-js
groq-js with typed GROQ Results
sanity-typed-schema-builder
Build Sanity schemas declaratively and get typescript types of schema values for free!
Completing sanity's developer experience with typescript (and more)!
npm install @sanity-typed/types
Typescript
Module System
Node Version
NPM Version
@sanity-typed/next-sanity@4.0.2
Updated on May 27, 2025
@sanity-typed/groq@3.0.2
Updated on May 27, 2025
@sanity-typed/faker@4.0.2
Updated on May 27, 2025
@sanity-typed/client-mock@4.0.2
Updated on May 27, 2025
@sanity-typed/client@5.0.2
Updated on May 27, 2025
@sanity-typed/groq-js@3.0.2
Updated on May 27, 2025
TypeScript (99.94%)
JavaScript (0.06%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
158 Stars
1,706 Commits
8 Forks
3 Watchers
5 Branches
5 Contributors
Updated on Jul 10, 2025
Latest Version
8.0.1
Package Id
@sanity-typed/types@8.0.1
Unpacked Size
60.36 kB
Size
12.55 kB
File Count
7
NPM Version
10.8.2
Node Version
20.19.1
Published on
May 24, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
2
2
Infer Sanity Document Types from Sanity Schemas
DocumentValues
1npm install sanity @sanity-typed/types
Use defineConfig
, defineType
, defineField
, and defineArrayMember
from this library exactly as you would from sanity
. Then, use InferSchemaValues
to get the typescript types!
product.ts
:
1// import { defineArrayMember, defineField, defineType } from "sanity"; 2import { 3 defineArrayMember, 4 defineField, 5 defineType, 6} from "@sanity-typed/types"; 7 8/** No changes using defineType, defineField, and defineArrayMember */ 9export const product = defineType({ 10 name: "product", 11 type: "document", 12 title: "Product", 13 fields: [ 14 defineField({ 15 name: "productName", 16 type: "string", 17 title: "Product name", 18 validation: (Rule) => Rule.required(), 19 }), 20 defineField({ 21 name: "tags", 22 type: "array", 23 title: "Tags for item", 24 of: [ 25 defineArrayMember({ 26 type: "object", 27 name: "tag", 28 fields: [ 29 defineField({ type: "string", name: "label" }), 30 defineField({ type: "string", name: "value" }), 31 ], 32 }), 33 ], 34 }), 35 ], 36});
sanity.config.ts
:
1import { structureTool } from "sanity/structure"; 2 3// import { defineConfig } from "sanity"; 4import { defineConfig } from "@sanity-typed/types"; 5import type { InferSchemaValues } from "@sanity-typed/types"; 6 7import { post } from "./schemas/post"; 8import { product } from "./schemas/product"; 9 10/** No changes using defineConfig */ 11const config = defineConfig({ 12 projectId: "59t1ed5o", 13 dataset: "production", 14 plugins: [structureTool()], 15 schema: { 16 types: [ 17 product, 18 // ... 19 post, 20 ], 21 }, 22}); 23 24export default config; 25 26/** Typescript type of all types! */ 27export type SanityValues = InferSchemaValues<typeof config>; 28/** 29 * SanityValues === { 30 * product: { 31 * _createdAt: string; 32 * _id: string; 33 * _rev: string; 34 * _type: "product"; 35 * _updatedAt: string; 36 * productName: string; 37 * tags?: { 38 * _key: string; 39 * _type: "tag"; 40 * label?: string; 41 * value?: string; 42 * }[]; 43 * }; 44 * // ... all your types! 45 * } 46 */
DocumentValues
While InferSchemaValues
gives you all the types for a given config keyed by type, sometimes you just want a union of all the SanityDocument
s. Drop it into DocumentValues
:
1import type { DocumentValues, InferSchemaValues } from "@sanity-typed/types"; 2 3const config = defineConfig({ 4 /* ... */ 5}); 6 7type SanityValues = InferSchemaValues<typeof config>; 8/** 9 * SanityValues === { [type: string]: TypeValueButSomeTypesArentDocuments } 10 */ 11 12type SanityDocuments = DocumentValues<SanityValues>; 13/** 14 * SanityDocuments === Each | Document | In | A | Union 15 */
Use definePlugin
from this library exactly as you would from sanity's own exports.
my-plugin.ts
:
1// import { defineField, definePlugin, defineType } from "sanity"; 2import { defineField, definePlugin, defineType } from "@sanity-typed/types"; 3 4/** No changes using definePlugin */ 5export const myPlugin = definePlugin({ 6 name: "plugin", 7 schema: { 8 types: [ 9 defineType({ 10 name: "myPlugin", 11 type: "object", 12 fields: [ 13 defineField({ 14 name: "baz", 15 type: "boolean", 16 }), 17 ], 18 }), 19 ], 20 }, 21});
sanity.config.ts
:
1// import { defineConfig, defineField, defineType } from "sanity"; 2import { defineConfig, defineField, defineType } from "@sanity-typed/types"; 3import type { InferSchemaValues } from "@sanity-typed/types"; 4 5import { myPlugin } from "./my-plugin"; 6 7const foo = defineType({ 8 name: "foo", 9 type: "document", 10 fields: [ 11 defineField({ 12 name: "bar", 13 type: "myPlugin", 14 }), 15 ], 16}); 17 18const config = defineConfig({ 19 schema: { 20 types: [foo], 21 }, 22 plugins: [myPlugin()], 23}); 24 25export default config; 26 27type SanityValues = InferSchemaValues<typeof config>; 28 29export type Foo = SanityValues["foo"]; 30/** 31 * Foo === { 32 * _createdAt: string; 33 * _id: string; 34 * _rev: string; 35 * _type: "foo"; 36 * _updatedAt: string; 37 * bar?: { 38 * _type: "myPlugin"; 39 * baz?: boolean; 40 * }; 41 * }; 42 **/
However, this export won't work for users who are using sanity's default methods. So that you won't have to define your plugin twice, we provide a castFromTyped
method, which converts the outputs of any define*
method to their native sanity
counterparts:
1import { castFromTyped, definePlugin } from "@sanity-typed/types"; 2 3export const myTypedPlugin = definePlugin({ 4 name: "plugin", 5 schema: { 6 types: [ 7 // ... 8 ], 9 }, 10}); 11 12// You'll likely want this as a default export as well! 13export const myUntypedPlugin = castFromTyped(myTypedPlugin);
sanity-typed also works directly with untyped definePlugin
directly, so you can import and use plugins directly (although they type as unknown
values). It doesn't handle untyped defineField
/defineArrayMember
/defineType
though, and some plugins export some for convenience. castToTyped
similarly converts untyped define*
methods to sanity-typed
versions with unknown
values:
1import { orderRankField } from "@sanity/orderable-document-list"; 2 3import { castToTyped } from "@sanity-typed/types"; 4 5const nav = defineType({ 6 name: "nav", 7 type: "document", 8 title: "Navigation", 9 fields: [ 10 castToTyped(orderRankField({ type: "nav" })), 11 defineField({ 12 name: "name", 13 type: "string", 14 title: "Name", 15 validation: (Rule) => Rule.required(), 16 }), 17 ], 18});
As your sanity driven application grows over time, your config is likely to change. Keep in mind that you can only derive types of your current config, while documents in your Sanity Content Lake will have shapes from older configs. This can be a problem when adding new fields or changing the type of old fields, as the types won't can clash with the old documents.
Ultimately, there's nothing that can automatically solve this; we can't derive types from a no longer existing config. This is a consideration with or without types: your application needs to handle all existing documents. Be sure to make changes in a backwards compatible manner (ie, make new fields optional, don't change the type of old fields, etc).
Another solution would be to keep old configs around, just to derive their types:
1const config = defineConfig({ 2 schema: { 3 types: [foo], 4 }, 5 plugins: [myPlugin()], 6}); 7 8const oldConfig = defineConfig({ 9 schema: { 10 types: [oldFoo], 11 }, 12 plugins: [myPlugin()], 13}); 14 15type SanityValues = 16 | InferSchemaValues<typeof config> 17 | InferSchemaValues<typeof oldConfig>;
This can get unwieldy although, if you're diligent about data migrations of your old documents to your new types, you may be able to deprecate old configs and remove them from your codebase.
Often you'll run into an issue where you get typescript errors in your IDE but, when building workspace (either you studio or app using types), there are no errors. This only occurs because your IDE is using a different version of typescript than the one in your workspace. A few debugging steps:
JavaScript and TypeScript Nightly
extension (identifier ms-vscode.vscode-typescript-next
) creates issues here by design. It will always attempt to use the newest version of typescript instead of your workspace's version. I ended up uninstalling it..vscode/settings.json
. Use TypeScript: Select TypeScript Version
to explictly pick the workspace version.The supported Typescript version is now 5.7.2 <= x <= 5.7.3. Older versions are no longer supported and newer versions will be added as we validate them.
The supported Typescript version is now 5.4.2 <= x <= 5.6.3. Older versions are no longer supported and newer versions will be added as we validate them.
as const
needed for certain types to infer correctlyLike mentioned in 6 no longer forces as const
, as const
is no required anywhere excent for references (otherwise they wouldn't reference correctly), but you will still want them in many places. Literals where it narrows the type are the usual candidates (ie string and number lists). But there are a few others, ie options.hotspot
for the image type needs it to be typed as true
to infer the hotspot fields. Due to typescript quirks, sometimes you'll need to add true as const
for it to infer correctly.
Until we get a proper understanding on how we can force typescript to infer the literals, we won't enforce it anywhere except for references. This is because it's a convenience everywhere else; references are rarely what you want without it.
as const
Besides for references, as const
is no longer needed for some of the types. While it will still type string literals when possible, it won't be required. You'll still need as const
if you actually want the literal types, but it was breaking too many valid workflows to require it.
as const
Similar to references, to get the right types out of a block, we'll need as const
with styles[number].value
and lists[number].value
. Also, marks.annotations[number]
now requires typing like other array members, ie defineArrayMember
:
1const foo = defineType({ 2 name: "foo", 3 type: "array", 4 of: [ 5 defineArrayMember({ 6 type: "block", 7 styles: [ 8- { title: "Foo", value: "foo" }, 9+ { title: "Foo", value: "foo" as const }, 10- { title: "Bar", value: "bar" }, 11+ { title: "Bar", value: "bar" as const }, 12 ], 13 lists: [ 14- { title: "Foo", value: "foo" }, 15+ { title: "Foo", value: "foo" as const }, 16- { title: "Bar", value: "bar" }, 17+ { title: "Bar", value: "bar" as const }, 18 ], 19 marks: { 20 annotations: [ 21- { 22+ defineArrayMember({ 23 name: "internalLink", 24 type: "object", 25 fields: [ 26- { 27+ defineField({ 28 name: "reference", 29 type: "reference", 30- to: [{ type: "post" }], 31+ to: [{ type: "post" as const }], 32- }, 33+ }), 34 ], 35- }, 36+ }), 37 ], 38 }, 39 }), 40 ], 41});
_InferValue
and AliasValue
Use InferSchemaValues
instead. Neither _InferValue
nor AliasValue
are directly usable, while InferSchemaValues
is the only real world use case.
_type
needs as const
For @sanity-typed/groq
to infer the right types from references, the reference type needs to carry the type it's referencing along with it. Unfortunately, it isn't deriving the literal so an as const
is needed.
1const product = defineType({ 2 name: "product", 3 type: "document", 4 title: "Product", 5 fields: [ 6 defineField({ 7 name: "foo", 8 type: "reference", 9- to: [{ type: "referencedType" }], 10+ to: [{ type: "referencedType" as const }], 11 }), 12 ], 13});
DocumentValue
to SanityDocument
1- import type { DocumentValue } from "@sanity-typed/types"; 2+ import type { SanityDocument } from "@sanity-typed/types";
InferSchemaValues<typeof config>
used to return a union of all types but now returns an object keyed off by type. This is because using Extract
to retrieve specific type was difficult. Object types would have a _type
for easy extraction, but all the other types were less reliable (i.e. arrays and primitives).
1export default config; 2 3type Values = InferSchemaValues<typeof config>; 4 5- export type Product = Extract<Values, { _type: "product" }> 6+ export type Product = Values["product"];
Types used to be inferred using InferValue<typeof type>
for easy exporting. Now, InferSchemaValues<typeof config>
needs to be used, and individual types keyed off of it. The reason for this is that only the config has context about aliased types, so InferValue
was always going to be missing those values.
1const product = defineType({ 2 name: "product", 3 type: "document", 4 title: "Product", 5 fields: [ 6 // ... 7 ], 8}); 9 10- export type Product = InferValue<typeof product>; 11 12const config = defineConfig({ 13 // ... 14 schema: { 15 types: [ 16 product, 17 // ... 18 ], 19 }, 20}); 21 22export default config; 23 24type Values = InferSchemaValues<typeof config>; 25 26+ export type Product = Values["product"];
You can still use _InferValue
but this is discouraged, because it will be missing the context from the config (and is removed in v5):
1const product = defineType({ 2 name: "product", 3 type: "document", 4 title: "Product", 5 fields: [ 6 // ... 7 ], 8}); 9 10- export type Product = InferValue<typeof product>; 11+ export type Product = _InferValue<typeof product>;
No vulnerabilities found.
No security vulnerabilities found.