Sanity plugin
Bring artworks, artists, shows, publications, and viewing rooms into a Sanity Studio with the sanity-plugin-gallery-platform package.
If your website runs on Sanity, the sanity-plugin-gallery-platform package
puts the platform's content models inside your Studio. Editors pick a work
from a search box, or import the roster as documents they can query with GROQ.
The platform stays the source of truth; Sanity holds a presentation copy.
npm install sanity-plugin-gallery-platformAdd the plugin
// sanity.config.ts
import { defineConfig } from "sanity";
import { galleryPlatform } from "sanity-plugin-gallery-platform";
export default defineConfig({
// …
plugins: [
galleryPlatform({
baseUrl: "https://gallery.example/api/v1",
token: "gpub_live_…",
}),
],
});The Studio bundle is visible to every Studio user, so the plugin never holds a secret key. Two configurations are safe:
- A publishable token. A
gpub_token reads the CORS-open public plane: the works you have opted in as publicly available, the artists behind them, your public viewing rooms, and the gallery profile. Mint one on the gallery's API page in the dashboard, next to the secret keys. - A proxy. Leave
tokenout and pointbaseUrlat a route on your own server that forwards to the API with yourgpk_key. The plugin then reads the full catalogue, including unpublished works, shows, publications, and images.
A gpk_ or opk_ key in the Studio config throws at load.
What you get
Document types. galleryPlatform.artwork, galleryPlatform.artist,
galleryPlatform.show, galleryPlatform.publication,
galleryPlatform.viewingRoom, and galleryPlatform.profile. Platform-owned
fields are read-only in the Studio. Add editable fields of your own:
galleryPlatform({
baseUrl,
token,
schema: {
include: ["artwork", "artist", "show"],
extend: {
artwork: [defineField({ name: "editorial", type: "text" })],
},
},
});A record picker. Put a galleryPlatform.reference field on any document
and editors search the gallery from inside the form:
defineField({
name: "featuredWork",
type: "galleryPlatform.reference",
options: { resources: ["artwork"] },
});The field stores the resource, the platform id, and a preview snapshot. Resolve the live record when you render, with the TypeScript SDK:
const { data } = await gp.artworks.get({ path: { id: page.featuredWork.id } });A Gallery Platform tool. A tab in the Studio lists your artworks, artists, shows, and publications with search. Select rows and import them as documents; importing again refreshes them.
Mirror the catalogue from a server
For a GROQ-queryable copy that stays current, run the sync from a server,
where a gpk_ key is allowed. The sync entry point works from a cron, a CI
job, or a webhook handler.
import { createClient } from "@sanity/client";
import {
syncGalleryToSanity,
syncOne,
} from "sanity-plugin-gallery-platform/sync";
const sanity = createClient({
projectId: "…",
dataset: "production",
token: process.env.SANITY_WRITE_TOKEN,
apiVersion: "2025-02-19",
useCdn: false,
});
const gallery = {
baseUrl: "https://gallery.example/api/v1",
token: process.env.GALLERY_PLATFORM_API_KEY, // gpk_…
};
// Everything, with images re-hosted as Sanity assets
await syncGalleryToSanity({ gallery, sanity, images: "upload", prune: true });
// One record, from a webhook
await syncOne({ gallery, sanity, resource: "artwork", id: event.resource_id });Each document's _id is gp- plus the platform id, so every run is
idempotent. The runner creates a document if it is missing, then patches only
the platform-owned fields, so anything you added with extend survives. With
prune: true it also deletes mirrors of records the platform no longer lists.
By default the runner skips private artists, shows, publications, and rooms,
and writes a price amount only where you publish it. Pass
includeUnpublished: true or includePrivatePrices: true to widen that.
Keep it current with webhooks
Subscribe to webhooks for artwork.updated,
artwork.deleted, artist.updated, and show.updated, verify the signature
with the SDK, and call syncOne with the event's resource id. A record the
platform no longer serves removes its mirror.
Rendering rules
- Show
price.centsonly whenprice.showPubliclyis true. - Prefer
images[].assetoverimages[].url. The URL is a signed link and expires; the asset is yours. artist,artists[].artist, andartworks[]are weak references. A document can point at a record you have not imported yet.
Build with Replit
Use Replit's Agent to build and host an app on your gallery data — with one scoped key that powers both the Agent at build time and your deployed app at runtime.
Connect an AI agent
Point an MCP client, an agent framework, or your own model at a gallery — the MCP connect URL, read-only and narrowed connections, how writes are confirmed, and the machine-readable surfaces an agent reads.