An opinionated guide to Sanity Studio
Sanity Studio is an incredibly flexible tool with near limitless customisation. Here's how I use it.
Go to An opinionated guide to Sanity StudioSometimes your content lives outside Sanity, but you need to store and reference it from the inside of the Content Lake.
For this, we can create a custom input component which queries an API, populates choices in a dropdown, and saves the value into a document.
If your content creators are currently copy-and-pasting values from an external service into a field, this guide is for you!
Any service with a queryable API can be turned into a dynamic set of options for a custom input.
Possible use cases include:
There's so much you can do with form components, so if you'd like to go deeper and learn more, read the docs.
Also, you should consider the value saved in this Component similar to a weak reference. If the value you retrieved from your API is removed from that service, your Sanity content will still contain that value, but it will be outdated.
In the code example below is a schema for a document type named product
, and within it, a string field named sku
with a placeholder React Component for the input.
Create a string field just like this sku
field and register it to your schema.
// ./schemas/product.ts
// Add this to the schema types registered in your sanity.config.ts file
import { defineField, defineType } from "sanity";
import ProductSelector from '../components/ProductSelector'
export default defineType({
name: 'product',
title: 'Product',
type: 'document',
fields: [
defineField({
name: 'title',
type: 'string',
}),
defineField({
name: 'sku',
title: 'SKU',
type: 'string',
components: {
input: ProductSelector,
},
}),
],
})
Separately, create a file for the ProductSelector
component:
// ./components/ProductSelector.tsx
export default function ProductSelector() {
return <div>ProductSelector</div>
}
Performing a fetch request inside a React component can be tricky. An asynchronous function needs to be run inside a useEffect()
hook, or you might install a fully-featured querying package like TanStack Query or SWR.
For simplicity in this tutorial, you'll need to install the package usehooks-ts
for its useFetch()
React Hook. It makes performing a fetch easy and provides loading and error states.
From the command line, in your Studio folder, run:
npm install usehooks-ts
For this example, you're going to use fakestoreapi.com for products, but you could add any publicly queryable API here. A third-party API is likely to need some authentication, and you will need to adjust the demo to suit.
Suppose your targeted API does not have configurable CORS settings to allow fetch requests from inside the Studio. In that case, you may also need to "wrap" your request in a serverless function to authenticate and return the data you need.
Your next step is adding data fetching, error, and loading states to our component. Plus, a nice confirmation of success – to make sure everything is working.
// ./components/ProductSelector.tsx
import {useFetch} from 'usehooks-ts'
import {Card, Spinner, Text} from '@sanity/ui'
const url = 'https://fakestoreapi.com/products'
const cardProps = {shadow: 1, padding: 3, radius: 2}
export default function ProductSelector() {
const {data, error} = useFetch<any[]>(url)
if (error)
return (
<Card tone="critical" {...cardProps}>
<Text>There has been an error</Text>
</Card>
)
if (!data)
return (
<Card tone="default" {...cardProps}>
<Spinner />
</Card>
)
return (
<Card tone="positive" {...cardProps}>
<Text>Success! {data.length} Products Found</Text>
</Card>
)
}
To see if this is working, you should now see a green box announcing "Success! 20 Products Found".
This is great! But, not very useful.
Let's make something useful.
Add the Select
component to the imports from @sanity/ui
and map over the data to create a dropdown menu showing the title and price of each item – but with the item's id
as the value is saved to the document.
// ./components/ProductSelector.tsx
// Update the imports to include Select
import {Select, Card, Spinner, Text} from '@sanity/ui'
// ... rest of the code
// Update the last return in the component
return (
<Select>
{data.map((item) => (
<option key={item.id} value={item.id}>
{item.title} (${item.price})
</option>
))}
</Select>
)
You should now have a select menu with options from the API.
Currently, selecting an item doesn't do anything
Using the code below, your new form component can patch the document each time the select menu is changed to save the item id
to the document in the sku
field.
Here's the final input component code:
// ./components/ProductSelector.tsx
import React from 'react'
import {StringInputProps, set, unset} from 'sanity'
import {useFetch} from 'usehooks-ts'
import {Select, Card, Spinner, Text} from '@sanity/ui'
const url = 'https://fakestoreapi.com/products'
const cardProps = {shadow: 1, padding: 3, radius: 2}
export default function ProductSelector(props: StringInputProps) {
// The onChange handler can write new changes to the field
const {onChange, value} = props
const {data, error} = useFetch<any[]>(url)
// This function will run each time the select menu is used
const handleChange = React.useCallback(
(event: React.FormEvent<HTMLSelectElement> | undefined) => {
const value = event?.currentTarget.value
// If the selected option has a value,
// it will be written to the document
// otherwise the field will be cleared
onChange(value ? set(value) : unset())
},
[onChange]
)
if (error)
return (
<Card tone="critical" {...cardProps}>
<Text>There has been an error</Text>
</Card>
)
if (!data)
return (
<Card tone="default" {...cardProps}>
<Spinner />
</Card>
)
return (
<Select onChange={handleChange} value={value}>
{data.map((item) => (
<option key={item.id} value={item.id}>
{item.title} (${item.price})
</option>
))}
</Select>
)
}
Create a new document, select an option, and you should see an ID number written to the sku
field.
There's much more you can do to improve the editing experience.
Some examples:
options
on the string field for a more composable custom input.{props.renderDefault(props)}
<Select>
for the more powerful Sanity UI <Autocomplete>
component for more intelligent searching and visual previews of returned data.Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.
Sanity Studio is an incredibly flexible tool with near limitless customisation. Here's how I use it.
Go to An opinionated guide to Sanity StudioIt can be useful for testing plugins, front ends, or other integrations to have a Sanity Studio populated with fake content.
Go to How to generate massive amounts of demo content for SanitySetup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioSummarise form progression by decorating the entire editing form for a document with a component loaded at the root level.
Go to Create a document form progress component