Gallery Platform API

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-platform

Add 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 token out and point baseUrl at a route on your own server that forwards to the API with your gpk_ 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.cents only when price.showPublicly is true.
  • Prefer images[].asset over images[].url. The URL is a signed link and expires; the asset is yours.
  • artist, artists[].artist, and artworks[] are weak references. A document can point at a record you have not imported yet.

On this page