⚠️ This is a very experimental project 🚨
iPad.deploys.theme.mov
Themer used to hand you an ESM URL to import straight into your Studio config:
import {theme} from 'https://themer.sanity.build/api/hues?preset=verdant'That endpoint keeps serving themes so existing Studios don't break, but it now logs a migration notice to the console on every load. The same colors are generated locally by @sanity/themer-legacy, with no network request and with TypeScript typings included:
npm install @sanity/themer-legacyimport {buildThemeFromUrl} from '@sanity/themer-legacy'
const theme = buildThemeFromUrl('https://themer.sanity.build/api/hues?preset=verdant')The URL is only a carrier for the hues, buildThemeFromUrl never fetches it. Keep using themer.sanity.build to preview and tweak your theme, then paste the URL it gives you into buildThemeFromUrl.
The full guide lives in the @sanity/themer-legacy README. It covers the createTheme + hues variant (now createTheme + parseHuesFromUrl), preset-only URLs like '?preset=verdant', and the one intentional difference from the hosted module (the __themer font flag).
If you already migrated to @sanity/themer/legacy, that subpath keeps working as a deprecated re-export of @sanity/themer-legacy until @sanity/themer@1.0 removes it. Swap the import to finish the move:
-import { buildThemeFromUrl } from '@sanity/themer/legacy'
+import { buildThemeFromUrl } from '@sanity/themer-legacy'Once migrated, nothing in your Studio should reference the hosted endpoint anymore. Run this at the root of your Studio repo:
rg 'themer\.sanity\.build' .If it still matches, delete whatever it finds. The setup snippets Themer used to generate left these behind:
- The static
import {theme} from 'https://themer.sanity.build/api/hues?...'insanity.config.ts, replaced bybuildThemeFromUrlabove. - The dynamic
await import(/* webpackIgnore: true */ 'https://themer.sanity.build/api/hues?...'), including theuseEffectinpages/index.tsxthat swapped the theme in at runtime. Pass the theme todefineConfigdirectly instead. themer.d.tswith itsdeclare module 'https://themer.sanity.build/api/hues?...'. The theme is typed by the package now, so any// @ts-expect-errorabove the old import goes too.experimental.urlImportsinnext.config.js.- The custom
_document.tsx/_document.jsthat only existed to add<link rel="modulepreload" href="https://themer.sanity.build/api/hues?...">. - A downloaded
theme.jsnext tosanity.config.ts.
pnpm workspace for the hosted Themer (themer.sanity.build) and @sanity/themer-legacy, the package that replicates it.
| Package | Description |
|---|---|
@sanity/themer-legacy |
The hosted Themer's theme generator as an npm package |
| App | Description |
|---|---|
apps/v1 |
themer.sanity.build (Next.js), including the deprecated /api/hues endpoint |
apps/storybook |
Storybook for @sanity/themer-legacy (localhost:6006 via pnpm dev) |
- Node.js
>=22.13 - pnpm
12(pinned viapackageManagerinpackage.json)
pnpm install
pnpm build
pnpm testpnpm dev # Storybook at http://localhost:6006
pnpm dev:v1 # themer.sanity.build at http://localhost:3001Storybook resolves @sanity/themer-legacy to its TypeScript source through the package exports, so it hot-reloads package edits without a rebuild.
| Script | What it does |
|---|---|
pnpm build |
Build @sanity/themer-legacy |
pnpm test |
Unit tests (@sanity/themer-legacy and apps/v1) |
pnpm test:browser |
Storybook browser tests (Chromium via Playwright) |
pnpm lint |
Lint + type-check (oxlint) |
pnpm format |
Format with oxfmt |
pnpm changeset |
Add a changeset for a release |
See CONTRIBUTING.md. Releases use Changesets: add a changeset on your PR; merging to main opens a “Version Packages” PR that publishes to npm when merged.
MIT — see LICENSE.