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-plugin-sgntech-review

v1.0.0

Published

Review blocks for Sanity Studio — score, verdict, pros and cons inside an article, with schema.org/Review markup for search results.

Readme


What you get

  • A reviewBlock for Portable Text: the reviewed item, a score, the verdict in one sentence, and two short lists — for and against.
  • One scale per Studio, not per block. Range and step live in the plugin configuration, so the reviews of a publication stay comparable instead of drifting between 4/5 and 85/100.
  • schema.org/Review out of the box. The renderer writes the JSON-LD next to the block, with ratingValue, bestRating, worstRating, positiveNotes and negativeNotes filled in from the same fields the reader sees.
  • Scores are optional. A review without a number stays a valid review — the markup simply omits reviewRating.
  • The author comes from the article; a field on the block covers guest reviews.
  • Design-agnostic. The score element carries data-value, data-max and data-percent, so stars, dots or a dial are a matter of your own CSS. The plugin ships structure only.

Installation

npm install sanity-plugin-sgntech-review

Requires Sanity Studio v5 or v6 and React 18 or 19. No runtime dependencies.

📦 On npm: sanity-plugin-sgntech-review

Usage in the Studio

// sanity.config.ts
import {defineArrayMember, defineConfig, defineField} from 'sanity'
import {reviewBlock} from 'sanity-plugin-sgntech-review'

export default defineConfig({
  // ...
  plugins: [
    reviewBlock({
      scale: {min: 1, max: 5, step: 0.5},
      itemTypes: ['release', 'concert'], // your own document types — optional
      defaultItemType: 'MusicPlaylist',
    }),
  ],

  schema: {
    types: [
      {
        type: 'document',
        name: 'article',
        fields: [
          defineField({
            type: 'array',
            name: 'body',
            of: [
              defineArrayMember({type: 'block'}),
              defineArrayMember({type: 'reviewBlock'}), // ← the review block
            ],
          }),
        ],
      },
    ],
  },
})

Options

reviewBlock({
  name: 'reviewBlock', // schema type name
  title: 'Review', // label in the insert menu
  scale: {min: 1, max: 5, step: 0.5}, // one scale for the whole Studio
  itemTypes: [], // document types the review may reference
  defaultItemType: 'CreativeWork', // preselected schema.org type
  allowReviewerOverride: true, // field for a guest reviewer
  allowReviewDate: true, // field for datePublished
})

Stars in Google — what actually works

Google shows review stars only for a fixed set of itemReviewed types. The Studio dropdown says which is which:

| Works | Does not get stars | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | Event, MusicRecording, MusicPlaylist, Book, Movie, Episode, CreativeWorkSeason, CreativeWorkSeries, Game, SoftwareApplication, Product, MediaObject, Course, Recipe, LocalBusiness, Organization | MusicAlbum, CreativeWork |

So a concert review marked up as Event is eligible. For an album, use MusicPlaylist or MusicRecording — MusicAlbum produces valid markup that simply never earns stars. Whether Google then shows them is Google's call; no plugin can promise that.

isStarsEligible(type) is exported if you want to warn editors in your own UI.

Stored value

{
  "_type": "reviewBlock",
  "_key": "a1b2",
  "itemName": "Zeit",
  "itemType": "MusicPlaylist",
  "itemUrl": "https://example.com/zeit",
  "score": 4.5,
  "verdict": "The best sounding record the band has made.",
  "positives": ["Sound", "Two strong singles"],
  "negatives": ["Three fillers in the middle"],
  "reviewDate": "2026-07-14"
}

Rendering in your frontend

import {PortableText} from '@portabletext/react'
import {createReviewPortableTextComponents} from 'sanity-plugin-sgntech-review/render'

const components = createReviewPortableTextComponents({
  scale: {min: 1, max: 5, step: 0.5}, // the same scale as in the Studio
  author: {name: article.author.name},
  publisher: {name: 'My Magazine'},
})

;<PortableText value={article.body} components={components} />

Referenced documents need a resolver, since only your site knows its own fields and routes:

createReviewPortableTextComponents({
  resolveReference: (item) => ({
    name: String(item.title ?? ''),
    url: `/releases/${(item.slug as {current?: string})?.current}`,
  }),
})

Fetch the reference along with the block:

body[]{..., _type == "reviewBlock" => {..., item->{title, slug}}}

Props

| Prop | Default | Purpose | | ---------------------- | ------------ | --------------------------------------------------- | | value | — | The reviewBlock value | | scale | 1–5 | Must match the Studio configuration | | author / publisher | — | Used in the markup; the block's reviewer field wins | | resolveReference | — | Turns a referenced document into name, url, image | | jsonLd | true | Emit the JSON-LD next to the block | | datePublished | — | Fallback when the block carries no date | | formatScore | plain digits | e.g. (score) => score.toLocaleString('de-DE') | | showItem | true | Show the name of the reviewed item | | injectStyles | true | Add the structural stylesheet | | labels | English | score, positives, negatives, by, reviewOf |

JSON-LD somewhere else

import {reviewJsonLd} from 'sanity-plugin-sgntech-review/render'

const data = reviewJsonLd(block, {scale, author, publisher})
// render it in <head> yourself
;<Review value={block} jsonLd={false} />

Styling

.sgn-review {
  --sgn-review-gap: 0.75rem;
  --sgn-review-meta-size: 0.875em;
  --sgn-review-meta-opacity: 0.75;
  --sgn-review-meter-height: 0.4rem;
  --sgn-review-meter-track: color-mix(in srgb, currentColor 15%, transparent);
  --sgn-review-meter-fill: currentColor;
  --sgn-review-notes-gap: 1.25rem;
}

Class names: sgn-review, __item, __score, __meter, __meter-fill, __verdict, __notes, __note-group, __note-title, __note-list, __byline, __link. The wrapper carries data-has-score, note groups carry data-tone="positive" / "negative".

Turning the score into stars needs no markup change — the percentage is right there:

.sgn-review__score {
  position: relative;
  font-size: 0;
}
.sgn-review__score::before {
  content: '★★★★★';
  font-size: 1.25rem;
  opacity: 0.25;
}
.sgn-review__score::after {
  content: '★★★★★';
  font-size: 1.25rem;
  position: absolute;
  left: 0;
  top: 0;
  overflow: hidden;
  /* width from data-percent, set via a small inline style or a CSS attr() polyfill */
}

Note that data-percent is the score's position on the scale: on a 1–5 scale, a 1 is 0 % — the bottom of the scale, not a fifth of it.

Accessibility

  • The score is written out as text; the meter is decorative and hidden from screen readers, so the value is announced once rather than twice.
  • For and against are real lists with headings, not a run-on sentence.
  • The block carries an accessible name built from the reviewed item.

Develop

npm install
npm test            # unit tests for the scale, the score and the JSON-LD
npm run lint
npm run build
npm run link-watch  # build + publish to a local yalc repo for Studio testing

Built with @sanity/plugin-kit.

License

MIT © SGNTech