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

v1.2.1

Published

A Sanity Studio plugin to manage SEO fields like meta titles, descriptions, and Open Graph tags for structured, search-optimized content.

Readme

🔍 Sanity SEO Fields Plugin

npm version npm downloads license GitHub issues GitHub stars

A comprehensive Sanity Studio v3 plugin to manage SEO fields like meta titles, descriptions, Open Graph tags, and X (Formerly Twitter) Cards for structured, search-optimized content.

✨ Features

  • 🎯 Meta Tags: Title, description, keywords, and canonical URLs
  • 📱 Open Graph: Complete social media sharing optimization
  • 🐦 X (Formerly Twitter) Cards: X-specific meta tags with image support
  • 🤖 Robots Control: Index/follow settings for search engines
  • 🖼️ Image Management: Optimized image handling for social sharing
  • 📋 Live Preview: Real-time SEO preview as you edit
  • 🔧 TypeScript Support: Full type definitions included
  • 📊 Custom Attributes: Flexible meta attribute system
  • Validation: Built-in character limits and best practices
  • 🎛️ Field Visibility: Hide sitewide fields on specific content types
  • 📊 SEO Health Dashboard: Studio-wide overview of SEO completeness with scores, issue highlights, and direct document links

📦 Installation

npm install sanity-plugin-seofields

or

yarn add sanity-plugin-seofields

🚀 Quick Start

1. Add the Plugin

Add the plugin to your sanity.config.ts (or .js) file:

import {defineConfig} from 'sanity'
import seofields from 'sanity-plugin-seofields'

export default defineConfig({
  name: 'your-project',
  title: 'Your Project',
  projectId: 'your-project-id',
  dataset: 'production',

  plugins: [
    seofields(), // Add the SEO fields plugin
    // ... other plugins
  ],

  schema: {
    types: [
      // ... your schema types
    ],
  },
})

2. Add SEO Fields to Your Documents

Add the SEO fields to any document type in your schema:

import {defineField, defineType} from 'sanity'

export default defineType({
  name: 'page',
  title: 'Page',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
    }),
    defineField({
      name: 'content',
      title: 'Content',
      type: 'text',
    }),
    // Add SEO fields
    defineField({
      name: 'seo',
      title: 'SEO',
      type: 'seoFields',
    }),
  ],
})

3. Using Individual SEO Components

You can also use individual components:

import {defineField, defineType} from 'sanity'

export default defineType({
  name: 'article',
  title: 'Article',
  type: 'document',
  fields: [
    // ... other fields

    // Individual SEO components
    defineField({
      name: 'openGraph',
      title: 'Open Graph',
      type: 'openGraph',
    }),
    defineField({
      name: 'twitterCard',
      title: 'X (Formerly Twitter) Card',
      type: 'twitter',
    }),
    defineField({
      name: 'metaAttributes',
      title: 'Custom Meta Tags',
      type: 'metaTag',
    }),
  ],
})

🎨 Available Schema Types

| Type | Description | Use Case | | --------------- | ---------------------------------- | -------------------------------- | | seoFields | Complete SEO package | Main SEO fields for any document | | openGraph | Open Graph meta tags | Social media sharing | | twitter | X (Formerly Twitter) Card settings | X-specific optimization | | metaTag | Custom meta attributes | Advanced meta tag management | | metaAttribute | Individual meta attribute | Building custom meta tags | | robots | Search engine directives | Control indexing and crawling |

🔧 Configuration Options

Basic Configuration

import seofields from 'sanity-plugin-seofields'

export default defineConfig({
  plugins: [
    seofields(), // Use default configuration
  ],
})

Advanced Configuration

You can customize field titles and descriptions, control SEO preview functionality, and manage field visibility:

import seofields, {SeoFieldsPluginConfig} from 'sanity-plugin-seofields'

export default defineConfig({
  plugins: [
    seofields({
      seoPreview: true, // Enable/disable SEO preview (default: true)
      fieldOverrides: {
        title: {
          title: 'Page Title',
          description: 'The main title that appears in search results',
        },
        description: {
          title: 'Meta Description',
          description: 'A brief description of the page content for search engines',
        },
        canonicalUrl: {
          title: 'Canonical URL',
          description: 'The preferred URL for this page to avoid duplicate content issues',
        },
        metaImage: {
          title: 'Social Media Image',
          description: 'Image used when sharing this page on social media',
        },
        keywords: {
          title: 'SEO Keywords',
          description: 'Keywords that describe the content of this page',
        },
      },
      // Hide sitewide fields on specific content types
      fieldVisibility: {
        page: {
          hiddenFields: ['openGraphSiteName', 'twitterSite'],
        },
        post: {
          hiddenFields: ['openGraphSiteName', 'twitterSite'],
        },
      },
      // Or hide fields globally
      defaultHiddenFields: ['openGraphSiteName', 'twitterSite'],
    } satisfies SeoFieldsPluginConfig),
  ],
})

Configuration Options

| Option | Type | Default | Description | | --------------------- | --------- | ------- | ------------------------------------------- | | seoPreview | boolean | true | Enable/disable the live SEO preview feature | | fieldOverrides | object | {} | Customize field titles and descriptions | | fieldVisibility | object | {} | Hide sitewide fields on specific post types | | defaultHiddenFields | array | [] | Hide sitewide fields globally |

Field Configuration

Each field in the fieldOverrides object can have:

  • title - Custom title for the field
  • description - Custom description/help text for the field

Available field keys:

  • title, description, canonicalUrl, metaImage, keywords, metaAttributes, robots
  • openGraphUrl, openGraphTitle, openGraphDescription, openGraphSiteName, openGraphType, openGraphImage
  • twitterCard, twitterSite, twitterCreator, twitterTitle, twitterDescription, twitterImage

Field Visibility Configuration

Control which fields are visible on different content types. You can hide any SEO field on any post type:

Available field keys:

  • title, description, canonicalUrl, metaImage, keywords, metaAttributes, robots
  • openGraphUrl, openGraphTitle, openGraphDescription, openGraphSiteName, openGraphType, openGraphImage
  • twitterCard, twitterSite, twitterCreator, twitterTitle, twitterDescription, twitterImage

ℹ️ Hiding openGraphImage or twitterImage also hides their URL and type variants to keep the editor experience consistent.

Example configurations:

// Hide fields globally
seofields({
  defaultHiddenFields: ['openGraphSiteName', 'twitterSite', 'keywords'],
})

// Hide fields on specific content types
seofields({
  fieldVisibility: {
    page: {
      hiddenFields: ['openGraphSiteName', 'twitterSite', 'keywords'],
    },
    post: {
      hiddenFields: ['openGraphSiteName', 'metaAttributes'],
    },
    product: {
      hiddenFields: ['canonicalUrl', 'robots'],
    },
  },
})

This is particularly useful when you want to:

  • Manage sitewide settings (like site name and X handle) in a dedicated Site Settings document
  • Simplify the editing experience by hiding fields that aren't relevant for certain content types
  • Create different SEO workflows for different content types

Field Specifications

Meta Title

  • Max Length: 70 characters (warning at 60)
  • Purpose: Search engine result headlines
  • Best Practice: Include primary keywords, keep under 60 chars

Meta Description

  • Max Length: 160 characters (warning at 150)
  • Purpose: Search result descriptions
  • Best Practice: Compelling summary with keywords

Canonical URL

  • Format: Must include protocol (https://)
  • Purpose: Signals the preferred URL when duplicate or paginated content exists
  • Best Practice: Mirror the resolved frontend route exactly to avoid mismatched indexing

Meta Image

  • Recommended Size: 1200x630px minimum 600x315px
  • Purpose: Default share image when Open Graph/Twitter images are absent
  • Best Practice: Provide descriptive alt text and keep file size under 5MB

Meta Attributes

  • Structure: Key/value pairs or key/image pairs
  • Purpose: Add bespoke <meta> tags (for example theme-color, author, verification tokens)
  • Best Practice: Avoid duplicating tags already generated elsewhere to limit head bloat

Keywords

  • Type: Array of short strings
  • Purpose: Editorial helper; not surfaced automatically to search engines
  • Best Practice: Keep entries concise (1-3 words) and limit to high-intent topics

Open Graph Image

  • Recommended Size: 1200x630px
  • Minimum Size: 600x315px
  • Aspect Ratio: 1.91:1
  • Formats: JPG, PNG, WebP

Open Graph URL

  • Purpose: Canonical URL for social media sharing
  • Format: Full URL with protocol (https://)
  • Best Practice: Use the preferred URL for the page to avoid duplicate content issues
  • Required: Should match the actual page URL for consistency

Open Graph Site Name

  • Purpose: Displays publisher name on share previews
  • Best Practice: Keep consistent with brand name used across marketing channels

Open Graph Type

  • Options: website, article, profile, book, music, video, product
  • Best Practice: Pick the narrowest type applicable to unlock platform-specific rendering

X (Formerly Twitter) Card Image

  • Summary Card: Minimum 120x120px
  • Large Image: Minimum 280x150px
  • Required: Alt text for accessibility

X (Formerly Twitter) Card Creator

  • Purpose: Attribution to content creator on X (formerly Twitter)
  • Format: X handle with @ symbol (e.g., @creator)
  • Usage: Identifies the individual author of the content
  • Best Practice: Use actual X handles for proper attribution

X (Formerly Twitter) Card Type

  • Options: summary, summary_large_image, app, player
  • Best Practice: Use summary_large_image for rich media, fall back to summary when imagery is square or minimal

X (Formerly Twitter) Site Handle

  • Purpose: Publisher attribution when multiple authors contribute
  • Format: X handle with @ symbol (e.g., @brand)
  • Best Practice: Configure once in site settings and hide on document types that inherit it

Robots Settings

  • Options: noIndex, noFollow
  • Purpose: Control whether pages are indexed or links followed by crawlers
  • Best Practice: Only enable when intentionally blocking content (for example gated pages or previews)

🎛️ Field Visibility Feature

The field visibility feature allows you to hide any SEO field on specific content types. This is perfect for managing sitewide settings in a dedicated Site Settings document or creating customized editing experiences for different content types.

Quick Example

// Hide specific fields on different content types
seofields({
  fieldVisibility: {
    page: {
      hiddenFields: ['openGraphSiteName', 'twitterSite', 'keywords'],
    },
    post: {
      hiddenFields: ['openGraphSiteName', 'metaAttributes'],
    },
    product: {
      hiddenFields: ['canonicalUrl', 'robots'],
    },
  },
})

Site Settings Integration

Create a Site Settings document to manage sitewide fields:

// schemas/siteSettings.ts
export default defineType({
  name: 'siteSettings',
  title: 'Site Settings',
  type: 'document',
  fields: [
    defineField({
      name: 'openGraphSiteName',
      title: 'Open Graph Site Name',
      type: 'string',
    }),
    defineField({
      name: 'twitterSite',
      title: 'X (Formerly Twitter) Site Handle',
      type: 'string',
    }),
  ],
})

Complete SEO Setup

// In your schema
defineField({
  name: 'seo',
  title: 'SEO & Social Media',
  type: 'seoFields',
  group: 'seo', // Optional: group in a tab
})

Custom Meta Tags

// For advanced users who need custom meta tags
defineField({
  name: 'customMeta',
  title: 'Custom Meta Tags',
  type: 'metaTag',
  description: 'Add custom meta attributes for specific needs',
})

Open Graph Only

// If you only need Open Graph
defineField({
  name: 'socialSharing',
  title: 'Social Media Sharing',
  type: 'openGraph',
})

📊 SEO Health Dashboard

The plugin includes a built-in SEO Health Dashboard tool accessible directly from Sanity Studio. It scans all documents that contain an seo field and gives you an at-a-glance picture of your site's SEO completeness.

🔑 License Key Required

The SEO Health Dashboard requires a valid license key to use. Good news: it's completely free during the current period (2–3 months). When we transition to a paid model, your existing key will remain valid for a one-time $10 fee.

Get your free license key →

Configuration

// Minimal — just add your license key
seofields({
  healthDashboard: {
    licenseKey: 'YOUR_LICENSE_KEY',
  },
})

// Full options — all nested under healthDashboard
seofields({
  healthDashboard: {
    // Required
    licenseKey: 'YOUR_LICENSE_KEY',

    // Studio nav tab
    tool: {
      title: 'SEO Audit', // tab label in Studio sidebar (default: 'SEO Health')
      name: 'seo-health-dashboard', // internal tool slug
    },

    // Dashboard page content
    content: {
      icon: '🔍', // emoji before the page heading
      title: 'SEO Audit', // page heading (default: tool.title)
      description: 'Track SEO quality across all published content.',
    },

    // Table columns
    display: {
      typeColumn: true, // show document type column (default: true)
      documentId: false, // show document _id (default: true)
    },

    // Document query
    query: {
      types: ['post', 'page'], // limit to specific document types
      requireSeo: true, // only include docs with seo != null (default: true)
      // groq: '*[seo != null] { _id, _type, title, seo, _updatedAt }',
      // ^ custom GROQ takes precedence over types + requireSeo
    },

    apiVersion: '2023-01-01', // Sanity API version (default: '2023-01-01')
  },
})

// Or disable the dashboard entirely
seofields({
  healthDashboard: false,
})

What it shows

| Feature | Details | | ------------------------ | -------------------------------------------------------------------------------- | | Summary stats | Total documents, average score, and count per health tier | | Per-document score | 0–95 score based on which SEO fields are filled in | | Color-coded badges | 🟢 Excellent (≥ 80) · 🟡 Good (≥ 60) · 🟠 Fair (≥ 40) · 🔴 Poor / Missing (< 40) | | Inline issues | Top 2 issues per document shown inline; overflow count displayed | | Direct document link | Click the document title to open it in the desk (new tab) | | Search & filter | Filter by health status, sort by score or title, and full-text search |

Scoring breakdown

| Field | Max Points | | ------------------- | ---------- | | Meta Title | 25 | | Meta Description | 20 | | OG Title | 15 | | OG Description | 10 | | Twitter Title | 10 | | Twitter Description | 10 | | Robots / No-Index | 5 | | Total | 95 |

Scoring logic: each field earns its full points when a non-empty value is present, zero when missing. query.groq lets you control exactly which documents are included in the audit.

🌐 Frontend Integration

Next.js Example

import Head from 'next/head'

export function SEOHead({seo}) {
  if (!seo) return null

  return (
    <Head>
      {seo.title && <title>{seo.title}</title>}
      {seo.description && <meta name="description" content={seo.description} />}

      {/* Open Graph */}
      {seo.openGraph?.title && <meta property="og:title" content={seo.openGraph.title} />}
      {seo.openGraph?.description && (
        <meta property="og:description" content={seo.openGraph.description} />
      )}
      {seo.openGraph?.url && <meta property="og:url" content={seo.openGraph.url} />}

      {/* Twitter */}
      {seo.twitter?.card && <meta name="twitter:card" content={seo.twitter.card} />}
      {seo.twitter?.site && <meta name="twitter:site" content={seo.twitter.site} />}
      {seo.twitter?.creator && <meta name="twitter:creator" content={seo.twitter.creator} />}

      {/* Robots */}
      {seo.robots?.noIndex && <meta name="robots" content="noindex" />}
      {seo.robots?.noFollow && <meta name="robots" content="nofollow" />}

      {/* Canonical URL */}
      {seo.canonicalUrl && <link rel="canonical" href={seo.canonicalUrl} />}
    </Head>
  )
}

React/Gatsby Example

import {Helmet} from 'react-helmet'

export function SEO({seo}) {
  return (
    <Helmet>
      <title>{seo?.title}</title>
      <meta name="description" content={seo?.description} />

      {/* Keywords */}
      {seo?.keywords && <meta name="keywords" content={seo.keywords.join(', ')} />}

      {/* Open Graph */}
      <meta property="og:title" content={seo?.openGraph?.title} />
      <meta property="og:description" content={seo?.openGraph?.description} />
      <meta property="og:url" content={seo?.openGraph?.url} />
      <meta property="og:type" content={seo?.openGraph?.type || 'website'} />

      {/* Twitter */}
      {seo?.twitter?.card && <meta name="twitter:card" content={seo.twitter.card} />}
      {seo?.twitter?.site && <meta name="twitter:site" content={seo.twitter.site} />}
      {seo?.twitter?.creator && <meta name="twitter:creator" content={seo.twitter.creator} />}
    </Helmet>
  )
}

📚 API Reference

Main Export

import seofields from 'sanity-plugin-seofields'

Schema Types

  • seoFields - Complete SEO fields object
  • openGraph - Open Graph meta tags
  • twitter - Twitter Card settings
  • metaTag - Custom meta tag collection
  • metaAttribute - Individual meta attribute
  • robots - Search engine robots settings

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

MIT © Desai Hardik

🆘 Support


Made with ❤️ for the Sanity community