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.4

Published

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

Downloads

59

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.

Setup

1. Add the Studio plugin

  1. Import aiImagePlugin in your sanity.config.ts.
  2. Add it to the plugins array.
  3. Set apiVersion.
  4. Add a generateButton target for each image field that should show a Generate button.
import { aiImagePlugin } from "@basementstudio/sanity-ai-image-plugin"
import { defineConfig } from "sanity"

export default defineConfig({
  // ...your existing config
  plugins: [
    aiImagePlugin({
      apiVersion: "2025-02-19",
      targets: [
        {
          id: "article-featured-image",
          type: "generateButton",
          documentType: "article",
          fieldPath: "featuredImage",
        },
      ],
    }),
  ],
})

With that config:

  • the AI Image Plugin settings tool is registered in Studio
  • the default asset source is enabled
  • article.featuredImage gets a Generate button

aiImagePlugin(...) options

| Option | Required | Default | Notes | | ------------------- | -------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | apiVersion | Yes | none | Sanity API version used by the plugin's client calls. | | targets | No | [] | Generate-button targets. Each matching target adds a Generate button above a Sanity image input. | | allowedModels | No | ["gemini-2.5-flash-image"] | Non-empty subset of SUPPORTED_AI_IMAGE_MODELS. The first entry becomes the default model in Studio unless settings or the route override it. | | apiEndpoint | No | "/api/ai-image-plugin" | Path the Studio calls when generating images. Change this if your route lives somewhere else. | | assetSource | No | enabled with built-in defaults | Adds the plugin to the Sanity image asset picker. Set false to disable it, or pass an object to customize labels and copy. | | frontendRateLimit | No | { maxRequests: 3, windowSecs: 60 } | Browser-side rolling window limiter shared across tabs via localStorage. Set false to disable it. | | settingsTool | No | { name: "ai-image-plugin-settings", title: "AI Image Plugin", registerSchemaType: false } | Lets you rename the settings tool or opt into registering the schema type automatically. |

targets[] generate-button fields

| Field | Required | Default | Notes | | ---------------------------- | -------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | id | Yes | none | Stable target ID. This is also how per-target settings are stored. | | type | Yes | none | Must be "generateButton". | | documentType | Yes | none | Sanity document type the target should match, like "article" or "homePage". | | fieldPath | Yes | none | Dot path to the image field, like "featuredImage" or "seo.ogImage". | | title | No | none | Friendly name shown in the settings tool and used as a fallback label in parts of the UI. | | description | No | "Generate an image with AI Image Plugin for this field." in the field UI | Short helper copy shown above the button and in the settings tool. | | dialogTitle | No | "Generate Image" | Title shown at the top of the generate dialog. | | promptLabel | No | "Custom prompt" | Label for the freeform editor prompt field in the dialog. | | promptPlaceholder | No | "Optional custom instructions for this image." | Placeholder text for the editor prompt field. | | suggestedContextFieldPaths | No | [] | Top-level document fields that should be preselected as context tags when the dialog opens. Editors can still toggle them per generation. |

assetSource fields

| Field | Required | Default | Notes | | ------------------- | -------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | enabled | No | true | Set false to disable the asset source while keeping the rest of the plugin enabled. | | id | No | "ai-image-plugin-asset-source" | Stable target ID used for asset-source-specific settings. | | title | No | "Image Asset Source" | Label shown in the asset picker. | | description | No | "Adds AI Image Plugin to the image asset picker." | Helper text shown in the settings UI. | | promptLabel | No | "Prompt" | Label for the asset source prompt field. | | promptPlaceholder | No | "Turn these references into a clean homepage hero image with warm daylight and subtle depth." | Placeholder text for the asset source prompt field. |

frontendRateLimit fields

| Field | Required | Default | Notes | | ------------- | -------- | ------- | ----------------------------------------------------------------- | | maxRequests | No | 3 | Maximum generation attempts allowed during the configured window. | | windowSecs | No | 60 | Rolling window length in seconds. |

settingsTool fields

| Field | Required | Default | Notes | | -------------------- | -------- | ---------------------------- | ---------------------------------------------------------------------------------------- | | name | No | "ai-image-plugin-settings" | Internal tool name. | | title | No | "AI Image Plugin" | Title shown in the Studio tool menu. | | registerSchemaType | No | false | Set true if you want the plugin to register its settings document schema type for you. |

2. Add the API route

  1. Create a POST route at /api/ai-image-plugin.
  2. Import handleAiImageRequest from the server entrypoint.
  3. Pass the shared secret and the provider API key(s) for the models you allow.
  4. If you customize allowedModels in Studio, mirror that same list here.
import { handleAiImageRequest } from "@basementstudio/sanity-ai-image-plugin/server"

export async function POST(request: Request) {
  return handleAiImageRequest(request, {
    apiKey: process.env.GEMINI_API_KEY!,
    sharedSecret: process.env.AI_IMAGE_PLUGIN_SHARED_SECRET!,
  })
}

If your route lives somewhere else, set apiEndpoint in aiImagePlugin(...) to match it.

handleAiImageRequest(request, options) fields

| Field | Required | Default | Notes | | ------------------------ | ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | sharedSecret | Yes | none | Must exactly match the secret saved in Studio. | | apiKey | Conditionally | none | Google API key. Use this for Google models, or use googleApiKey instead. | | googleApiKey | Conditionally | none | Provider-specific alias for Google models. If both googleApiKey and apiKey are present, googleApiKey wins. | | openAiApiKey | Conditionally | none | Required when you allow OpenAI models such as gpt-image-1. | | allowedModels | No | If model is set, [model]; otherwise ["gemini-2.5-flash-image"] | Non-empty subset of the package-supported models. Keep this in sync with the Studio config if you expose multiple models. | | model | No | First entry in resolved allowedModels | Route-level default model. It must also be present in allowedModels. | | enforceSameOrigin | No | true | Rejects browser requests whose Origin does not match the API route origin. | | geminiApiUrl | No | Google Generative Language API default base URL | Optional override for Google requests. | | maxReferenceFileBytes | No | 8 * 1024 * 1024 | Maximum size for a single reference image upload. | | maxReferences | No | 5 | Maximum number of reference images accepted per request. | | maxTotalReferenceBytes | No | maxReferences * maxReferenceFileBytes | Maximum combined size of all reference images in one request. |

3. Set the env vars your route uses

Add the variables your server route reads. For the minimal example above, you only need the shared secret and a Google API key.

| Variable | Required | Default | Notes | | ------------------------------- | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | AI_IMAGE_PLUGIN_SHARED_SECRET | Yes | none | Shared secret used by your app route. Use a long random string. | | GEMINI_API_KEY | Conditionally | none | Required when the active model is a Google model such as gemini-2.5-flash-image. | | OPENAI_API_KEY | Conditionally | none | Required when the active model is an OpenAI model such as gpt-image-1. | | AI_IMAGE_MODEL | No | none | Optional project-level env var you can read in your own route if you want to choose the default model from the environment. The plugin does not read this automatically. |

4. Configure the shared secret in Studio

  1. Start your Sanity Studio.
  2. Open the AI Image Plugin tool in the Studio navigation.
  3. Click Configure shared secret.
  4. Paste the same value you set in AI_IMAGE_PLUGIN_SHARED_SECRET.
  5. Save it.

The Studio stores this secret separately from the normal plugin settings document and sends it as the x-ai-image-plugin-secret header when generating images. If you ever rotate the secret, update it in both places.

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.

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.

Frontend rate limit

The Studio UI applies a browser-side rolling-window limiter by default across both generation entrypoints in the same browser profile.

Default behavior:

  • 3 generation attempts per 60 seconds
  • shared through localStorage, so refreshes and other tabs see the same block
  • configurable with frontendRateLimit in sanity.config
  • disabled entirely with frontendRateLimit: false

Only requests that are actually sent to the plugin API route consume quota. Local validation failures inside Studio do not.

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 "...".

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