npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@basementstudio/sanity-ai-image-plugin

v0.1.0

Published

Portable Sanity Studio plugin and server helper for AI image generation.

Readme

sanity-ai-image-plugin

Portable Sanity Studio plugin and server helper for generating images with Gemini, OpenAI, and other package-supported image models and dropping the result straight into Sanity image fields.

This package is intentionally self-contained so it can be copied into a new repo or published later without dragging along an app-specific schema layout.

This repo is source-first while it is being developed here:

  • package exports point at src/*

What It Owns

  • a Studio plugin via aiImagePlugin(...)
  • a plugin-owned settings document and settings tool
  • a generic image asset source
  • optional generate-button targets for specific image fields
  • a server helper export for the app-owned API route

Install Shape

The package exposes two entrypoints:

  • sanity-ai-image-plugin
  • sanity-ai-image-plugin/server

Consumer Setup

1. Add the Studio plugin

import { defineConfig } from "sanity";
import {
  SUPPORTED_AI_IMAGE_MODELS,
  createArticleFeaturedImageTarget,
  aiImagePlugin,
} from "sanity-ai-image-plugin";

const allowedModels = [
  SUPPORTED_AI_IMAGE_MODELS[0],
  SUPPORTED_AI_IMAGE_MODELS[2],
] as const;

export default defineConfig({
  // ...your existing config
  plugins: [
    aiImagePlugin({
      apiVersion: "2025-02-19",
      allowedModels: [...allowedModels],
      assetSource: true,
      targets: [
        createArticleFeaturedImageTarget(),
        {
          id: "home-page-featured-image",
          type: "generateButton",
          title: "Home Page Featured Image",
          documentType: "homePage",
          fieldPath: "featuredImage",
          suggestedContextFieldPaths: ["title", "description"],
        },
      ],
    }),
  ],
});

2. Add the thin app-owned route.

import {
  handleAiImageRequest,
  SUPPORTED_AI_IMAGE_MODELS,
} from "sanity-ai-image-plugin/server";

const allowedModels = [
  SUPPORTED_AI_IMAGE_MODELS[0],
  SUPPORTED_AI_IMAGE_MODELS[2],
] as const;

export async function POST(request: Request) {
  return handleAiImageRequest(request, {
    allowedModels: [...allowedModels],
    apiKey: process.env.GEMINI_API_KEY,
    openAiApiKey: process.env.OPENAI_API_KEY,
    sharedSecret: process.env.AI_IMAGE_PLUGIN_SHARED_SECRET!,
    // Shared-secret auth and same-origin protection are enabled by default.
    // Optional overrides:
    // model: process.env.AI_IMAGE_MODEL as (typeof allowedModels)[number],
    // maxReferenceFileBytes: 8 * 1024 * 1024,
    // maxTotalReferenceBytes: 5 * 8 * 1024 * 1024,
  })
}

3. Set env vars

  • GEMINI_API_KEY is required when you allow Google models
  • OPENAI_API_KEY is required when you allow OpenAI models
  • AI_IMAGE_PLUGIN_SHARED_SECRET is required for the app-owned route
  • AI_IMAGE_MODEL is optional and can still override the server default

4. Configure the shared secret in Studio

Open the AI Image Plugin settings tool and configure the same shared secret value there.

The plugin stores that Studio-side value with @sanity/studio-secrets, so it is fetched at runtime for logged-in Studio users instead of being bundled into the Studio source code.

Supported Models

The package has an internal supported-model registry. In this first pass it contains exactly:

  • gemini-2.5-flash-image
  • gemini-3.1-flash-image-preview
  • gpt-image-1

Each installation can opt into any non-empty subset of those models with the ordered allowedModels config. The first allowed model becomes the fallback default for both the Studio UI and the server helper unless the settings document or route overrides it.

Settings Model

The plugin owns one settings document:

  • _id: aiImagePlugin.settings
  • _type: aiImagePluginSettings

It stores:

  • globalModel
  • globalPrompt
  • globalReferenceImages
  • targetConfigs[]

Each target config can override:

  • targetId
  • prompt
  • referenceImages

Behavior

Server helper

The server helper requires a valid shared secret and same-origin browser requests by default.

That means requests are accepted only when:

  1. the request includes the correct x-ai-image-plugin-secret header
  2. the browser Origin matches the API route origin exactly

Same-origin matches look like this:

  • http://localhost:3000/studio -> http://localhost:3000/api/ai-image-plugin
  • https://myapp.vercel.app/studio -> https://myapp.vercel.app/api/ai-image-plugin

The helper does not inspect the /studio path directly because browser Origin headers only include the scheme, host, and port. The shared secret is managed from the plugin settings tool and stored separately from the plugin's normal prompt/reference-image settings document.

By default it also enforces:

  • 8 MiB maximum per reference image
  • a combined reference-image cap of maxReferences * 8 MiB
  • the requested model must be both package-supported and present in the route's configured allowedModels

If your framework supports route-level body limits, keep those enabled too.

Asset source

The generic asset source composes:

  1. global prompt
  2. asset-source target prompt
  3. editor prompt

Reference images are combined from:

  1. global reference images
  2. asset-source target reference images
  3. local editor-uploaded reference images

The asset-source model picker starts on:

  1. settings.globalModel when it is present and allowed
  2. otherwise the first configured allowedModels entry

Editors can override that selection for the current request before generating.

Generate button targets

Generate-button targets match against:

  • documentType
  • fieldPath

When matched, the plugin renders a Generate button above the normal Sanity image input.

Targets can also declare:

  • suggestedContextFieldPaths

When the dialog opens, the plugin inspects the current document schema and shows eligible top-level document fields as toggle tags. In this first pass, eligible field types are:

  • string
  • text
  • number
  • boolean
  • date
  • datetime
  • slug

Suggested context field paths are only default-on tags. They are filtered to fields that exist on the current document type, and editors can toggle them on or off for each generation.

The generate dialog also includes a model picker. It starts from the same global default resolution as the asset source, but editors can switch to a different allowed model for that one generation request.

Prompt composition order is:

  1. global prompt
  2. target prompt
  3. optional selected document context
  4. optional editor prompt

Selected document context is built as generic lines such as:

  • The field called "title" has content "...".

Optional Preset

createArticleFeaturedImageTarget(...) is an optional preset for article.featuredImage.

It feeds the model:

  • shared global settings
  • target-specific article settings
  • editor-selectable document-derived title + excerpt context suggestions
  • optional editor prompt

If you do not use that preset, the package still works as a generic asset source and generic generate-button plugin.

Desk Structure

The plugin does not require custom desk structure wiring. If a consuming app uses a custom structure and wants to hide aiImagePluginSettings from the normal document list, that is optional and app-owned.

PNG Normalization

All reference images are converted to PNG before they are sent to the server helper. That includes:

  • locally uploaded reference files
  • stored settings images downloaded from Sanity
  • new images uploaded through the settings tool

Development

bun run check
bun run build
bun test