Sanity Studio is highly customizable. You can override default components, customize the layout, modify branding, and extend functionality through configuration.
Studio component hierarchy
The studio has several levels of customization:
- Studio components: Layout, navbar, logo, tool menu
- Form components: Field inputs, items, previews
- Document components: Actions, badges, inspectors
Customizing studio components
Import the component prop types you need:
import {defineConfig, type LayoutProps, type NavbarProps, type LogoProps} from 'sanity'
Wrap the default layout with your own component:
function CustomLayout(props: LayoutProps) {
return (
<Box height="fill">
{/* Add custom elements before */}
{props.renderDefault(props)}
{/* Add custom elements after */}
</Box>
)
}
Add banners or modify the navbar:
import {Stack, Card, Text, Flex} from '@sanity/ui'
function CustomNavbar(props: NavbarProps) {
return (
<Stack>
<Card padding={4} tone="primary">
<Flex align="center" gap={4}>
<Text weight="semibold" size={1}>
Custom banner message
</Text>
</Flex>
</Card>
{props.renderDefault(props)}
</Stack>
)
}
Register them in your studio configuration:
export default defineConfig({
// ... project settings
studio: {
components: {
layout: CustomLayout,
navbar: CustomNavbar,
logo: CustomLogo,
toolMenu: CustomToolMenu,
},
},
})
Component override examples
Custom logo
Conditional rendering
import {type LogoProps} from 'sanity'
function CustomLogo(props: LogoProps) {
return (
<div style={{padding: '0.5rem'}}>
<img src="/logo.svg" alt="My Brand" />
</div>
)
}
function CustomNavbar(props: NavbarProps) {
const isDevelopment = process.env.NODE_ENV === 'development'
return (
<Stack>
{isDevelopment && (
<Card padding={2} tone="caution">
<Text size={1}>Development mode</Text>
</Card>
)}
{props.renderDefault(props)}
</Stack>
)
}
You can override input components for specific field types:
import {type StringInputProps} from 'sanity'
function CustomStringInput(props: StringInputProps) {
return (
<Stack space={3}>
{props.renderDefault(props)}
<Text size={1} muted>
Character count: {props.value?.length || 0}
</Text>
</Stack>
)
}
Apply it to a specific field:
defineField({
name: 'title',
type: 'string',
components: {
input: CustomStringInput,
},
})
Plugin-based customization
Create reusable customizations with plugins:
import {definePlugin} from 'sanity'
export const studioComponentsPlugin = definePlugin({
name: 'studio-components-plugin',
studio: {
components: {
layout: (props) => (
<Box height="fill" data-testid="custom-layout">
{props.renderDefault(props)}
</Box>
),
navbar: (props) => (
<Box data-testid="custom-navbar">
{props.renderDefault(props)}
</Box>
),
},
},
})
Then use it in your config:
import {studioComponentsPlugin} from './plugins/studio-components'
export default defineConfig({
plugins: [studioComponentsPlugin()],
})
Branding and theming
Customize colors, fonts, and visual styling:
import {defineConfig} from 'sanity'
export default defineConfig({
projectId: 'your-project-id',
dataset: 'production',
theme: {
fonts: {
text: {
family: 'Inter, sans-serif',
},
code: {
family: 'JetBrains Mono, monospace',
},
},
},
})
Customizing the workspace
Configure workspace-level settings:
export default defineConfig({
name: 'default',
title: 'My Studio',
projectId: 'your-project-id',
dataset: 'production',
// Customize the base path
basePath: '/studio',
// Add custom metadata
subtitle: 'Content management',
// Configure tools
plugins: [
structureTool({
icon: BookIcon,
name: 'content',
title: 'Content',
}),
],
})
Asset source customization
Add custom asset sources for images and files:
import {defineConfig} from 'sanity'
import {unsplashAssetSource} from 'sanity-plugin-asset-source-unsplash'
export default defineConfig({
form: {
image: {
assetSources: [unsplashAssetSource],
},
},
})
Document-level customization
Customize how documents behave:
export default defineConfig({
document: {
// Custom actions
actions: (prev, context) => {
// Filter or add actions based on document type
if (context.schemaType === 'post') {
return [...prev, myCustomAction]
}
return prev
},
// Custom badges
badges: (prev, context) => {
if (context.schemaType === 'author') {
return [CustomBadge, ...prev]
}
return prev
},
// New document templates
newDocumentOptions: (prev) => {
return [...prev, {
id: 'author-from-template',
title: 'Author from template',
type: 'author',
icon: UserIcon,
}]
},
},
})
Modify the document toolbar:
import {defineConfig} from 'sanity'
export default defineConfig({
studio: {
components: {
unstable_formActions: (props) => {
return (
<>
{props.renderDefault(props)}
<Button text="Custom action" />
</>
)
},
},
},
})
Using React context for state
Share state between custom components:
import {createContext, useContext} from 'react'
const StudioContext = createContext<{theme: 'light' | 'dark'}>({
theme: 'light',
})
function CustomLayout(props: LayoutProps) {
return (
<StudioContext.Provider value={{theme: 'dark'}}>
<Box height="fill">
{props.renderDefault(props)}
</Box>
</StudioContext.Provider>
)
}
function CustomNavbar(props: NavbarProps) {
const {theme} = useContext(StudioContext)
return (
<Card tone={theme === 'dark' ? 'transparent' : 'default'}>
{props.renderDefault(props)}
</Card>
)
}
Always use props.renderDefault(props) to preserve default functionality while adding your customizations.
Next steps