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

v2.1.0

Published

A collection of utilities and helpers for the Sanity.io Studio.

Readme

Sanity Pills

Latest version Dependency status Open issues

A collection of utilities formulated to provide positive experiences in the Sanity Studio.

Note: these pills are non-prescription—use them as they fit your needs and ignore them as they don’t!

Pills

Documentation on each of the utilities provided follow.

Authoring fields with objects

The fields helper allows authoring of field schemas in a more succinct, scannable manner. By way of example:

import { fields } from "sanity-pills"

{
  name: "example",
  type: "object",
  fields: fields({
    // a required string
    name: { required: true },

    // an optional number
    age: {
      type: "number",
    },

    // a string that is only required when `age` is below 18
    guardianName: {
      required: ({ parent }) => parent.age < 18,
    },
  }),
}

This pill will use the object key as the name of the field, and if no title is provided in the value definition, a title will be inferred from the name (similar to default Sanity Studio behavior, less the warnings).

This behaves more or less expected, converting the object into an array with the one notable callout being that required relies on the validation property on the field and specifying validation explicitly will override the required functionality (i.e. Sanity Pills will not merge them).

Validated image fields

There are no built-in validators for image dimensions in the Sanity Studio, but these are often valuable. It is straightforward enough to parse the image dimensions out of the image _id field, but Sanity Pills can handle this for you.

import { createImageField, fields } from "sanity-pills"

export default {
  name: "example",
  type: "document",
  fields: fields({
    headshot: createImageField({
      validations: {
        required: true,
        minWidth: 500,
        minHeight: 500,
      },
      warnings: {
        minWidth: 1000,
        minHeight: 1000,
      },
    }),
  }),
}

This example illustrates a required headshot image that will be invalid unless the original image dimensions are at minimum 500x500. It also shows a warning message suggesting that the best results would be achieved with an image of 1000x1000 when either of the original dimensions is smaller.

This pill also supports validations of maxWidth and maxHeight, though they are less likely to be required.

On a related note, Sanity Pills also exposes the function used to parse a Sanity asset ID as decodeAssetId. It's documented later in this document.

Slugs representing URL paths

If you happen to want your slugs to be valid URL paths, complete with the initial slash and a trailing slash, possibly with a required prefix, while supporting the “generate” button, you might appreciate this pill—it does just that.

It enforces the following rules:

  1. Slugs are required
  2. Slugs must start with /
  3. Slugs must end with /
  4. The first non-slash character has to be a letter, except when the slug is /404/
  5. Slugs have to be lowercase alphanumeric characters, plus hyphens and forward slashes
  6. If the prefix option is supplied to createSlugField, the slug must begin with /<prefix>/

Here's are a couple examples:

import { createSlugField, fields } from "sanity-pills"

export default {
  name: "example",
  type: "document",
  fields: fields({
    // optional string field
    name: {},

    // slug with a “generate” button that sources from name
    slug: createSlugField({ source: "name" }),

    // produces a prefixed URL like /blog/hello/ and validates
    // that the prefixes is included as expected
    scopedSlug: createSlugField({
      prefix: "blog",
      source: "name",
    }),
  }),
}

You can use an async function for source (it's passed through unchanged), but for now prefix only supports static string values.

Oh, and if you want to roll your own slug but want a handy slugify routine, you can use createSlug from the Sanity Pills package. E.g.:

import { createSlug } from "sanity-pills"

export default {
  // …
  slug: {
    type: "slug",
    options: {
      source: "name",
      slugify: createSlug,
    },
    required: true,
  },
}

Validating block content

Portable Text is a powerful way for editors to author non-trivial, rich data structures in a platform agnostic way, but it's easy to wind up with poor-quality content represented impeccably. As an array of nested blocks, enforcing common things like no trailing whitespace, links always having associated URLS, or even just that real content is provided can be cumbersome.

This pill makes validating Portable Text fields easier by including common validation patterns out of the box and supporting custom extensions.

Sanity Pills ships with two built in block validators ready to use: all and optional. Both of these enforce the following validations, and all also enforces the presence of a value for the field.

  1. No empty blocks: an empty paragraph, for example
  2. No newlines: prevents single blocks (rendered as <p> tags) by default from containing newlines (rendered as <br /> tags)
  3. No terminating whitespace: disallows spaces at the beginning or end of a block
  4. No missing links: links must have a valid href property
  5. No unstyled blocks: each block needs a style set (e.g. normal or h1)
  6. No stacked marks: disallows having text that is bold and italic or italic and strikethrough, etc. while allowing stacking of custom marks
  7. No marks on headings: disallows any marks to be used on blocks with a style starting with h

If you’re happy with this list, you can use either of the default block validators like so:

import { defaultBlockValidators } from "sanity-pills"

{
  type: "array",
  of: [{ type: "block" }],
  validation: defaultBlockValidators.all, // or .optional
}

It's entirely likely that this very opinionated set of validations is not entirely suitable for your use case, or that you need to add an additional custom validator to the list. Not to worry, that's possible like so:

import { createBlockValidator } from "sanity-pills"

const yourValidator = createBlockValidator({
  // enable a couple of the built in validations
  noEmptyBlocks: true,
  validateLinks: true,

  // add a custom validation that only allows heading blocks
  noTextAllowed: (blocks) => {
    const errorPaths = (blocks || [])
      .filter(
        (block) =>
          block._type === "block" &&
          block.style.match(/^h[1-6]$/)
      )
      .map((block, index) => [{ _key: block._key }] || [index])

    return (
      errorPaths.length === 0 || {
        message: "Must be styled as a heading",
        paths: errorPaths,
      }
    )
  }
})

// use your validator like so
{
  type: "array",
  of: [{ type: "block" }],
  validation: yourValidator,
}

Using Portable Text in a preview

Since block content is stored as an array, you can't use it directly when customizing previews. Instead you have to convert it to a string, but ignore everything that isn't readily convertible to a string. That's what blockPreview does:

import { blockPreview } from "sanity-pills"

export default {
  // …
  preview: {
    select: {
      title: "title",
      copy: "copy",
    },
    prepare: ({ title, copy }) => ({
      title,
      subtitle: blockPreview(copy),
    }),
  },
}

Parsing an asset ID

Sanity assigns stable, informative IDs for asset uploads, including the format and, for images, the dimensions of the original file. These can be easily parsed using the decodeAssetId export from Sanity Pills.

import { decodeAssetId } from "sanity-pills"

const {
  dimensions: { width, height },
  format,
} = decodeAssetId(someImageAssetId)

Preventing duplicates in arrays of references

Arrays of references are pretty common, and the usual cases for them typically only expect a single instance of any selected document. This wee routine enhances the experience of choosing references by removing any documents that are already in the array.

import { noDuplicateRefs } from "sanity-pills"

const field = {
  type: "array",
  of: [
    {
      type: "reference",
      to: [{ type: "someDocumentType" }],
      options: {
        filter: noDuplicateRefs,
      },
    },
  ],
}

Joining path segments into a slash-delimited URL

Typical path joining routine that prevents doubling up slashes but won't remove doubled slashes in a string you pass in.

import { urlJoin } from "sanity-pills"

urlJoin("/foo/", "/bar/") // #=> "/foo/bar/"
urlJoin("foo", "bar") // #=> "foo/bar"
urlJoin("/f/o/o/", "/b/a/r/") // #=> "/f/o/o/b/a/r/"
urlJoin("/", "/", "/", "/", "/") // #=> "/"

License

Copyright ©2022 Corey Ward. Available under the MIT License.