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

@sanity-labs/sanity-plugin-resonance

v0.2.1

Published

Run Resonance audience tests from a Sanity Studio document inspector.

Downloads

284

Readme

@sanity-labs/sanity-plugin-resonance

Ask your audiences what they think of a piece of content before you publish it, from inside Sanity Studio.

Status: pre-alpha. This is experimental software from Sanity Labs. The configuration, the panel, and the prompt it sends can change at any time without notice, and it depends on a Resonance deployment that has been set up to accept Studio sessions. Use it to try things, not to build on yet. If you do try it, tell us what you wanted it to do and didn't.

What it does

This plugin puts your Resonance audiences one click away from the document an editor is working on.

Open a document, click Resonance in the header, and run an audience test. Each audience reads the content as it stands right now, including unpublished changes, and comes back with a score from 1 (not for me) to 5 (I would read, save, or share this), an explanation in its own voice, and a short list of what would move the score. Results arrive as each audience finishes, usually within a minute. If the piece is already live, the audiences are also told what changed, so the feedback is about the revision rather than the whole article.

The last run is remembered per document, so reopening the panel shows what the audiences said last time and whether the content has changed since.

Install

npm install @sanity-labs/sanity-plugin-resonance
// sanity.config.ts
import {resonance} from '@sanity-labs/sanity-plugin-resonance'
import {defineConfig} from 'sanity'

export default defineConfig({
  // ...
  plugins: [
    resonance({
      accountUid: '<your Resonance account uid>',
      documents: ['post', 'article'],
    }),
  ],
})

That is the whole default setup. Editors get:

  • A Resonance button on post and article documents and nowhere else.
  • Content converted to text automatically from the schema: the title, a standfirst if there is one, and every Portable Text field in order. Images, embeds and custom blocks become short placeholders, so nothing but the words goes out.
  • Comparison with the published version whenever they test a draft of something that is live.
  • Every audience on the Resonance account, and Resonance's own neutral prompt.

accountUid is the Resonance account the Studio tests against; your Resonance contact gives you it. Before anyone can run a test, Resonance must accept Sanity Studio sessions, and each editor's email must have access to that account. Your Resonance contact sets that up too; the panel tells editors when access is missing. docs/auth.md has the details, including the one Studio setting worth knowing about (auth.loginMethod: 'token'). The Studio itself needs sanity 5.30 or newer.

Make it better

Audiences give sharper feedback when they know where they are reading, who is talking, and what changed. Everything below is optional and applies per document type. Turn a type name in documents into a defineResonanceDocument({...}) and add what you know. Anything shared by every type goes in defaults.

Tell audiences where it lives

import {defineResonanceDocument, resonance} from '@sanity-labs/sanity-plugin-resonance'

resonance({
  accountUid: '<your Resonance account uid>',
  documents: [
    defineResonanceDocument({
      type: 'post',
      channel: 'the Sanity blog',
      url: ({document}) => {
        const slug = document.slug as {current?: string} | undefined
        return slug?.current ? `https://www.sanity.io/blog/${slug.current}` : null
      },
    }),
  ],
})

Audiences are then told they came across the piece "on the Sanity blog (https://…)". For a draft, the URL is where it will be; when comparing, it is where the earlier version is live. Return null from url while the slug is empty.

Say who publishes it

resonance({
  accountUid: '<your Resonance account uid>',
  defaults: {
    source: 'Sanity, the company that makes the product being discussed',
  },
  documents: ['post', 'article'],
})

Readers weigh vendor content differently from an independent review. Without this, audiences tend to spend part of their answer asking who wrote it.

Choose the audiences

defineResonanceDocument({
  type: 'article',
  audiences: ['developer', 'technical-leader'],
})

Audience slugs come from your Resonance account. Editors can narrow the list further per run from the panel's options; the plugin config sets the starting point.

Control the comparison

defineResonanceDocument({type: 'post', compare: 'none'})

'published' (the default) compares a draft against the live version when they differ. 'none' never compares.

Bring your own text

import {defaultSerialize, defineResonanceDocument} from '@sanity-labs/sanity-plugin-resonance'

defineResonanceDocument({
  type: 'landingPage',
  serialize: (document, ctx) => {
    const base = defaultSerialize(document, ctx)
    if (!base) return null
    const promise = typeof document.promise === 'string' ? `\n\n> ${document.promise}` : ''
    return {...base, content: `${base.content}${promise}`}
  },
})

Use this when the automatic conversion misses the point of a type: a page built from modules, a custom block that carries the meaning, fields with unusual names. Return {title?, content} as markdown or plain text, or null when there is nothing to review yet. A serializer that handles several types (switching on document._type) goes in defaults.serialize; a per-type serialize overrides it. url works the same way.

Before writing one, check what the default produces. It already reads the words out of custom blocks (titles, labels, body text, nested cards and tabs), renders code as fences and rows of cells as tables, and turns images and embeds into short markers, while skipping links, ids and presentation fields.

Write the prompt yourself

defineResonanceDocument({
  type: 'post',
  question: 'A colleague sent you this from the Sanity blog. Would you forward it, and to whom?',
})

The string replaces the plugin's composed prompt for that type (or every type, in defaults.question).

A complete example

import {defineResonanceDocument, resonance} from '@sanity-labs/sanity-plugin-resonance'
import {defineConfig} from 'sanity'

const post = defineResonanceDocument({
  type: 'post',
  channel: 'the Sanity blog',
  url: ({document}) => {
    const slug = document.slug as {current?: string} | undefined
    return slug?.current ? `https://www.sanity.io/blog/${slug.current}` : null
  },
})

const article = defineResonanceDocument({
  type: 'article',
  channel: 'the Sanity documentation',
  audiences: ['developer', 'technical-leader'],
  compare: 'none',
})

export default defineConfig({
  // ...
  plugins: [
    resonance({
      accountUid: '<your Resonance account uid>',
      defaults: {
        source: 'Sanity, the company that makes the product being discussed',
      },
      documents: [post, article],
    }),
  ],
})

Every option is listed in docs/configuration.md.

How it works

When an editor runs a test, the plugin turns the displayed document into text with the type's serializer, works out what the audiences should be told about where it lives and what changed, and sends one request to Resonance. Resonance puts it in front of each audience and the panel polls until they have all answered.

What the audiences see is the text plus a short framing, built from what you configured:

You have come across this on the Sanity blog (https://www.sanity.io/blog/…) in the course of your work. It is published by Sanity, the company that makes the product being discussed. The earlier version is what is currently live there; the new version is an unpublished revision. Read it and react to it as yourself.

Configure nothing and only Resonance's neutral prompt is used. Draft status is never mentioned unless a comparison is being made.

Running the same content with the same settings twice returns the same test rather than asking the audiences again. Change the text, the compare setting, the audiences, or the framing, and you get a fresh run.

Requests go straight from the editor's browser to Resonance, authenticated with the editor's own Sanity session; there is no Resonance credential in the Studio, and the Sanity token is sent nowhere but Resonance. The plugin stores nothing except, in the browser, which test ran last for each document and the editor's option choices, keyed by Resonance host so two deployments never share them. docs/auth.md explains the model and what needs to be in place on the Resonance side.

The panel

  • Audience test card: a one-line explanation and the Run audience test button. The cog opens options: Compare with the published version (only offered when a live version exists and differs) and the list of audiences to send to, all selected by default. Choices are remembered.
  • Content has changed since the audiences last tested it. appears under the card when the document no longer matches the last run.
  • While running: "3 of 5 audiences have responded" and a provisional score.
  • Results: the mean score ("3.4 / 5 score across 5 audiences"), then one card per audience with its verdict and score ("Great — 4/5"), its reasoning, and a collapsed Recommendations list. Audiences that could not finish say so.

If something is not set up yet, the panel shows a short explanation instead of the run card:

| You see | It means | What to do | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | | Sign out and back in to link Resonance to your Sanity account. | This Studio signed in with a cookie, so there is no session token to hand to Resonance. | Sign out and back in from the user menu. Studio owners: auth.loginMethod: 'token' makes it stick. | | Couldn't reach Resonance. | The request got no response (Resonance is down, the apiUrl is wrong, or the network is blocking it). | Retry. If it persists, check apiUrl, then your Resonance contact. | | Resonance couldn't verify your Sanity session. | Resonance rejected the session. | Sign out and in; if it persists, tell your Resonance contact. | | You're not in Resonance yet. | Your email has not been granted the configured Resonance account. | Ask your Resonance admin, then Check again. | | This account has no audiences yet. | Resonance has no audiences defined for this account. | Define them in Resonance first. |

Docs

  • docs/configuration.md: every option, the document config, context types, the exact request, how the prompt is composed, and the idempotency rule.
  • docs/auth.md: how the Studio authenticates to Resonance, what needs to be in place on the Resonance side, and what editors see when something is missing.
  • docs/development.md: building the plugin, trying it in a Studio with hot reload, and the toolchain quirks.
  • skills/sanity-plugin-resonance/SKILL.md: an agent skill for installing and configuring the plugin. npx skills add sanity-labs/sanity-plugin-resonance.

License

MIT © Sanity Labs