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

@crawl-me-maybe/sanity-plugin-seo

v0.1.1

Published

<img width="1590" height="408" alt="sanity-plugin-thumb" src="https://github.com/user-attachments/assets/1069b4d7-7a3c-4ceb-b538-b0f3bcbe4c71" /> <br/>

Downloads

279

Readme

@crawl-me-maybe/sanity-plugin-seo

SEO fields, global defaults, and social preview cards for Sanity Studio.

Built for Sanity v5 and v6

Designed to pair with @crawl-me-maybe/meta on the frontend. The globalSeoSettings document and pageMetadata object use the same field names and shapes that buildMetadata() expects — query your content, resolve image URLs in GROQ, and pass the result straight through.

Table of contents

Install

npm install @crawl-me-maybe/sanity-plugin-seo
pnpm add @crawl-me-maybe/sanity-plugin-seo
bun add @crawl-me-maybe/sanity-plugin-seo
yarn add @crawl-me-maybe/sanity-plugin-seo

Features

  • Global defaults — a globalSeoSettings singleton document that feeds fallback values to all page-level fields; editors always see what fallback is being displayed
  • Page metadata — a pageMetadata object type with meta description, meta image, and search indexing controls that each show and override the global default
  • Social preview cards — live editable previews for Facebook, Twitter/X, LinkedIn, and Google Search, built from page data
  • Favicon browser-tab preview — renders your favicon and site title in a mock browser tab with light/dark toggle
  • Robots.txt rules editor — structured rule builder with a read-only robots.txt preview tab

Getting started

Plugin configuration

Pass options to the plugin to enable or disable individual features. All options default to true.

// sanity.config.ts
import crawlMeMaybeSeo from "@crawl-me-maybe/sanity-plugin-seo";

export default defineConfig({
  plugins: [
    crawlMeMaybeSeo({
      global: {
        favicon: true,   // favicon field + browser-tab preview in Global Settings
        robots: true,    // robots.txt rule builder in Global Settings
      },
      page: {
        searchIndexing: true, // noIndex / noFollow controls on page metadata
        canonicalUrl: true,   // canonical URL override on page metadata
      },
    }),
  ],
});

| Option | Type | Default | Description | |---|---|---|---| | global.favicon | boolean | true | Includes the favicon field (with browser-tab preview) in Global SEO Settings | | global.robots | boolean | true | Includes the robots rules builder in Global SEO Settings | | page.searchIndexing | boolean | true | Includes noIndex / noFollow controls on the pageMetadata field | | page.canonicalUrl | boolean | true | Includes the canonical URL field on pageMetadata. Disable if you manage canonical via a custom reference field on the document |

Custom canonical URLs

By default, pageMetadata includes a canonicalUrl field for explicit URL overrides. Disable it when you prefer a reference to another document:

// sanity.config.ts
crawlMeMaybeSeo({
  page: {
    canonicalUrl: false,
  },
});

Then add your own field on the page document:

defineField({
  name: "canonical",
  type: "reference",
  to: [{ type: "page" }, { type: "post" }],
})

Resolve the referenced document to a URL in your frontend query and pass it to @crawl-me-maybe/meta with disableSelfCanonical: true. When the built-in field is enabled, leave it empty and @crawl-me-maybe/meta generates a self-referential canonical from siteUrl + slug automatically.

Adding SEO fields to a page document

Include the pageMetadata object type on any document schema. Global defaults are displayed automatically in Studio; use @crawl-me-maybe/meta's buildMetadata() on the frontend to apply the same merge logic.

// schemas/page.ts
import { defineType, defineField } from "sanity";

export default defineType({
  name: "page",
  title: "Page",
  type: "document",
  fields: [
    defineField({
      name: "title",
      title: "Title",
      type: "string",
    }),
    defineField({
      name: "slug",
      title: "Slug",
      type: "slug",
      options: { source: "title" },
    }),
    defineField({
      name: "seo",
      title: "SEO",
      type: "pageMetadata", // registered by the plugin
    }),
  ],
});

Each pageMetadata field shows the live global default as a placeholder when the field is empty, and renders social preview cards (Facebook, Twitter/X, LinkedIn, Google) in a tabbed panel.

Studio structure — surfacing Global SEO Settings

Register the plugin, then add the globalSeoSettings singleton to your Studio's structure so editors can reach it:

// sanity.config.ts
import { defineConfig } from "sanity";
import { structureTool } from "sanity/structure";
import crawlMeMaybeSeo from "@crawl-me-maybe/sanity-plugin-seo";

export default defineConfig({
  plugins: [
    structureTool({
      structure: (S) =>
        S.list()
          .title("Content")
          .items([
            // ... your other list items ...
            S.divider(),
            S.listItem()
              .title("Global SEO Settings")
              .child(
                S.document()
                  .schemaType("globalSeoSettings")
                  .documentId("globalSeoSettings")
              ),
          ]),
    }),
    crawlMeMaybeSeo(),
  ],
});

Favicons

Browser-tab preview for dynamic favicons with theme-switching button. Can be disabled by setting global.favicon to false.

Robots.txt

Preview-enabled array field for robots.txt entries. Disabled by setting global.robots to false.

🧠 Note: This plugin only generates a preview, you are still responsible for exposing and formatting this to your frontend.


Frontend integration

This plugin only stores SEO metadata inside Sanity. Your app is responsible for reading these fields and rendering meta tags, canonical URLs, schema markup, robots.txt, and sitemap.xml.

@crawl-me-maybe/meta handles the metadata side — merging page and global defaults, title templates, Open Graph, Twitter cards, and canonical URLs. @crawl-me-maybe/sitemap and @crawl-me-maybe/schema cover sitemaps, robots.txt, and JSON-LD.

With @crawl-me-maybe/meta

The plugin schemas map directly to buildMetadata() inputs — no adapter layer required:

| Plugin schema | buildMetadata argument | Notes | |---|---|---| | globalSeoSettings | globalSeoDefaults (GlobalSeoSettings) | siteTitle, pageTitleTemplate, siteUrl, metaDescription, twitterHandle | | globalSeoSettings.defaultMetaImage | defaultMetaImage | Resolve to a URL string in GROQ (defaultMetaImage.asset->url) | | globalSeoSettings.favicon | faviconUrl | Resolve to a URL string in GROQ (favicon.asset->url) | | Page title + slug | page.title, page.slug | Required for title templates and self-canonical URLs | | pageMetadata.description | page.description | Falls back to globalSeoSettings.metaDescription | | pageMetadata.metaImage | page.metaImage | Resolve to a URL string in GROQ; falls back to defaultMetaImage | | pageMetadata.canonicalUrl | page.canonicalUrl | Path or full URL; empty values become a self-canonical from siteUrl + slug | | pageMetadata.searchIndexing | page.searchIndexing | noIndex / noFollow become a robots meta tag |

Page query:

*[_type == "page" && slug.current == $slug][0]{
  title,
  "slug": { "current": slug.current },
  "description": seo.description,
  "canonicalUrl": seo.canonicalUrl,
  "searchIndexing": seo.searchIndexing,
  "metaImage": seo.metaImage.asset->url
}

Global defaults query:

*[_type == "globalSeoSettings"][0]{
  siteTitle,
  pageTitleTemplate,
  metaDescription,
  siteUrl,
  twitterHandle,
  "defaultMetaImage": defaultMetaImage.asset->url,
  "faviconUrl": favicon.asset->url
}

Next.js example:

// app/[slug]/page.tsx
import type { Metadata } from "next";
import { buildMetadata } from "@crawl-me-maybe/meta";
import { toNextMeta } from "@crawl-me-maybe/meta/next";
import { client } from "@/lib/sanity";

export async function generateMetadata({ params }): Promise<Metadata> {
  const page = await client.fetch(PAGE_QUERY, { slug: params.slug });
  const seoDefaults = await client.fetch(GLOBAL_SEO_QUERY);

  return toNextMeta(buildMetadata(page, seoDefaults));
}

See the @crawl-me-maybe/meta docs for Nuxt, raw HTML, canonical URL options, and build-time overrides.


Schemas

globalSeoSettings document

A singleton document that provides site-wide defaults. Page-level fields display these values in Studio when empty; @crawl-me-maybe/meta's buildMetadata() applies the same fallbacks on the frontend.

| Field | Sanity type | Required | Validation | Notes | |---|---|---|---|---| | siteTitle | string | ✅ | Required | Injected into pageTitleTemplate via {siteTitle} | | pageTitleTemplate | string | ✅ | Required | Custom token input; initial value {pageTitle} - {siteTitle} | | siteUrl | url | ✅ | Must start with https:// | Used when generating canonical URLs, Open Graph URLs, and sitemap entries. | | metaDescription | metaDescription | — | Warn < 120 or > 160 chars | Default description displayed by page metadata fields | | defaultMetaImage | metaImage | — | — | Default OG image displayed by page metadata fields | | favicon | image | — | — | Browser-tab preview in Studio. Controlled by global.favicon option | | twitterHandle | string | — | Must start with @ | Social group | | logo | image | — | — | Used for Organization / WebSite schema markup | | advanced.robots | robots[] | — | Paths must start with / | robots.txt rule builder with preview tab. Controlled by global.robots option |


pageMetadata object

Add this type to any document that needs per-page SEO. Fields display the active global default as placeholder when empty.

| Field | Sanity type | Required | Validation | Notes | |---|---|---|---|---| | description | metaDescription | — | Warn < 120 or > 160 chars | Overrides globalSeoSettings.metaDescription | | canonicalUrl | string | — | Path (/about) or https:// URL | Optional override; leave empty for auto self-canonical on the frontend. Controlled by page.canonicalUrl option | | searchIndexing | searchIndexing | — | — | noIndex / noFollow controls. Controlled by page.searchIndexing option | | metaImage | metaImage | — | — | Overrides globalSeoSettings.defaultMetaImage; shows thumbnail of active default when empty |


Shared field types

These named types are registered by the plugin and can be reused in your own schemas:

| Type name | Base type | Description | |---|---|---| | metaDescription | text | 3-row textarea with 120–160 char warnings | | metaTitle | string | Single-line title field with 50–60 char warnings | | metaImage | image | Standard image field for OG/social use | | favicon | image | Image with browser-tab preview component | | searchIndexing | object | noIndex + noFollow boolean controls | | robots | array | Rule builder with userAgent / allow / disallow fields and robots.txt preview |