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 🙏

© 2025 – Pkg Stats / Ryan Hefner

sanity-plugin-seofields

v1.0.10

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

📦 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',
})

🌐 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