Skip to main content
seofields
docs

Configuration Options

All options are passed inside the healthDashboard key of seofields(). The only required option is licenseKey — everything else has sensible defaults.

Quick Reference

OptionTypeDefaultDescription
licenseKeyrequiredstringRequired to unlock the dashboard.
apiVersionstring'2023-01-01'Sanity Content Lake API version used for the dashboard query.
tool.titlestring'SEO Health'Label shown on the Studio navigation tab.
tool.namestring'seo-health-dashboard'Internal tool slug used by Sanity Studio.
content.iconstringEmoji shown before the heading inside the dashboard page.
content.titlestringCustom heading inside the dashboard page.
content.descriptionstringSubtitle shown beneath the heading in the dashboard.
content.loadingLicensestring'Verifying license…'Text shown while the license key is being verified.
content.loadingDocumentsstring'Loading documents…'Text shown while the document list is being fetched.
content.noDocumentsstring'No documents found'Text shown when the query returns zero results (or search/filter matches nothing).
showTypeColumnbooleantrueShow or hide the document type column in the results table.
showDocumentIdbooleantrueShow or hide the Sanity document _id under each title.
display.typeColumndeprecatedbooleantrueShow or hide the document type column in the results table.
display.documentIddeprecatedbooleantrueShow or hide the Sanity document _id under each title.
query.typesstring[]Limit the dashboard to specific Sanity document type names.
query.requireSeobooleantrueOnly include documents that have a non-null seo field.
query.groqstringFully custom GROQ query — overrides query.types and query.requireSeo.
typeDisplayLabelsRecord<string, string>Map raw _type values to human-readable labels.
typeLabelsdeprecatedRecord<string, string>Map raw _type values to human-readable labels.
typeColumnMode'badge' | 'text''badge'Render the Type column as a dynamically-coloured badge or plain text.
titleFieldstring | Record<string, string>'title'Document field to use as the display title — supports per-type mapping.
previewModebooleanfalseLoad realistic dummy data instead of querying your dataset — great for demos and screenshots.
structureToolstringName of the Structure tool to use when opening documents from the dashboard.
exportboolean | { enabled?: boolean; formats?: Array<'csv' | 'json'> }trueEnable export buttons so editors can download the currently filtered document list as CSV or JSON.
compactStatsbooleanfalseReplace the 6-card stats grid with a single row of inline stat pills to save vertical space.
getDocumentBadge(doc) => { label, bgColor?, textColor?, fontSize? } | undefinedCallback that returns a custom badge shown next to the document title.
docBadgedeprecated(doc) => { label, bgColor?, textColor?, fontSize? } | undefinedCallback that returns a custom badge shown next to the document title.

Query precedence & defaults

The dashboard builds its document list using one of two strategies, evaluated in this order:

  1. 1
    query.groqhighest priority. When this is set, the dashboard runs your GROQ string verbatim and ignores query.types and query.requireSeo entirely. Your query must return documents with at least: _id, _type, title, seo, _updatedAt.
  2. 2
    query.typesrestricts the built-in query to only the listed document types. Combine with query.requireSeo to also toggle whether documents without an seo field are included.
  3. 3
    Default (nothing set) — the dashboard queries *[seo != null], returning every document across all types that has a non-null seo field.
Tip: If you set both query.groq and query.types, query.groq wins and query.types is silently ignored.

Detailed Reference

licenseKeystringREQUIRED

Without a valid licenseKey the SEO Health tab will not render any data. Keys follow the SEOF-XXXX-XXXX-XXXX format and are tied to your Sanity project ID.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
  },
})
apiVersionstringdefault: '2023-01-01'

The default is stable and works for all current Sanity projects. Only change this if you need a specific API version for compatibility reasons.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    apiVersion: '2024-01-01',
  },
})
tool.titlestringdefault: 'SEO Health'

The text that appears as the tool's tab name in the Sanity Studio sidebar. The heading inside the dashboard page is controlled separately via content.title.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    tool: { title: 'SEO Audit' },
  },
})
tool.namestringdefault: 'seo-health-dashboard'

The internal identifier for the tool. Only change this if you register multiple instances or have a naming conflict with another Studio tool.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    tool: { name: 'my-seo-tool' },
  },
})
content.iconstringdefault:

Rendered next to the title inside the dashboard page. Any single emoji works.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    content: { icon: '🔍' },
  },
})
content.titlestringdefault:

Replaces the default page heading inside the dashboard. The Studio nav tab label is controlled separately via tool.title.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    content: { title: 'Content SEO Audit' },
  },
})
content.descriptionstringdefault:

Adds a short description below the dashboard heading. Useful for giving editors context about what the dashboard shows.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    content: { description: 'Track SEO quality across all published blog posts.' },
  },
})
content.loadingLicensestringdefault: 'Verifying license…'

Replaces the default spinner message displayed while the dashboard checks your license key against the server. Useful for matching your team's language or branding.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    content: { loadingLicense: 'Checking your plan…' },
  },
})
content.loadingDocumentsstringdefault: 'Loading documents…'

Replaces the default spinner message while the GROQ query is in flight. Helpful when fetching a large content set and you want to set editor expectations.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    content: { loadingDocuments: 'Fetching your content, hang tight…' },
  },
})
content.noDocumentsstringdefault: 'No documents found'

Displayed in place of the table when no documents match the current query, search, or filter. You can tailor this message to match your content model — e.g. tell editors which document types are expected to have SEO fields.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    content: { noDocuments: 'No pages with SEO fields yet — try publishing one first.' },
  },
})
showTypeColumnbooleandefault: true

Defaults to true. Set to false to hide the _type column — useful when all your documents are the same type.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    showTypeColumn: false,
  },
})
showDocumentIdbooleandefault: true

Defaults to true. Exposes the raw document ID in the table. Set to false for a cleaner editor UI.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    showDocumentId: false,
  },
})
display.typeColumnbooleandefault: trueDEPRECATED showTypeColumn

Renamed in v1.3.2 to the top-level showTypeColumn. The old nested form still works for backwards compatibility but will be removed in a future major release.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    showTypeColumn: false, // ← replacement key
    // display: { typeColumn: false }, // ← old form, still works
  },
})
display.documentIdbooleandefault: trueDEPRECATED showDocumentId

Renamed in v1.3.2 to the top-level showDocumentId. The old nested form still works for backwards compatibility but will be removed in a future major release.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    showDocumentId: false, // ← replacement key
    // display: { documentId: false }, // ← old form, still works
  },
})
query.typesstring[]default:

Restricts the built-in query to only the listed document types. By default (nothing set) all types with a non-null seo field are included. Ignored entirely when query.groq is also set — see the precedence callout above.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    query: { types: ['post', 'page', 'product'] },
  },
})
query.requireSeobooleandefault: true

When true (the default), documents where seo is null or missing are excluded from the results. Set to false to include all documents of the queried types even if they have no seo field. This option is only meaningful when query.types is set — it has no effect when query.groq is used, since your custom query controls filtering entirely.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    query: { requireSeo: false },
  },
})
query.groqstringdefault:

Highest priority — when set, query.types and query.requireSeo are both ignored entirely. The dashboard runs your GROQ string verbatim. Use this when you need full control over filtering (e.g. exclude drafts, filter by a nested field, or join across document types).

Required keys in query returnall 5 mandatory
KeyTypePurpose
_idstringSanity document ID — used to build the Studio edit link.
_typestringDocument type — drives the Type column and type-filter dropdown.
titlestringDisplay name shown in the dashboard table. Use titleField to map a different field name per type.

Must be a plain string. If your document stores the title as Portable Text (block content) or any other non-string type, use pt::text() in your GROQ projection to convert it first — e.g. "title": pt::text(title). When a non-string value is detected at render time, the dashboard shows an amber ⚠ badge in the Title column instead of a broken link, prompting you to fix the projection.

seoobjectSEO field object — the dashboard reads all sub-fields to compute the 0–100 score.
_updatedAtstring (ISO 8601)Last-modified timestamp — shown in the Last Updated column.
sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    query: {
      groq: `*[_type == "post" && defined(title) && !(_id in path("drafts.**"))] {
        _id, _type, title, seo, _updatedAt
      }`,
    },
  },
})
typeDisplayLabelsRecord<string, string>default:

Used in both the Type column and the Type filter dropdown. Any _type without an entry falls back to the raw _type string. Useful when your document type names are technical (e.g. productDrug) and you want editors to see friendly names (e.g. Products).

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    typeDisplayLabels: {
      productDrug: 'Products',
      singleCondition: 'Condition',
      landingPage: 'Landing Page',
    },
  },
})
typeLabelsRecord<string, string>default: DEPRECATED typeDisplayLabels

Renamed in v1.3.2 to typeDisplayLabels. The old key still works for backwards compatibility but will be removed in a future major release.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    typeDisplayLabels: {    // ← replacement key
      productDrug: 'Products',
    },
    // typeLabels: { ... }, // ← old key, still works
  },
})
typeColumnMode'badge' | 'text'default: 'badge'

badge (default) renders each type as a coloured pill. Badge colours are assigned dynamically: a deterministic hash of the _type string picks a colour from a 16-colour palette, so every type — including custom ones — automatically gets a distinct, consistent colour with no configuration needed. text renders it as plain inline text — useful for dense layouts or when badge colours feel distracting.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    typeColumnMode: 'text',
  },
})
titleFieldstring | Record<string, string>default: 'title'

By default the dashboard reads the title field on every document. If some of your document types store the display name in a different field (e.g. name, label, heading), set titleField to that field name. Pass a string to use the same field across all types, or a Record<string, string> to specify a different field per type. Unmapped types always fall back to title.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    // Same field for all types
    titleField: 'name',

    // — or — per-type mapping
    titleField: {
      post: 'title',
      product: 'name',
      category: 'label',
    },
  },
})
previewModebooleandefault: false

When true, the dashboard bypasses license validation and skips all Sanity queries. Instead it renders a set of sample documents covering the full score range (excellent → missing). An amber 'Preview Mode' badge is shown in the dashboard header so editors know the data is not real. Useful for documentation screenshots, showcasing the dashboard to stakeholders, or testing the UI before your content model is in place.

sanity.config.ts
seofields({
  healthDashboard: {
    previewMode: true,
  },
})
structureToolstringdefault:

When you have multiple structure tools registered in your Sanity Studio, the generic intent resolver always picks the first one. Set structureTool to the name of the tool that contains your monitored documents (e.g. 'common') and clicking a document title will navigate directly to /{basePath}/{structureTool}/intent/edit/id=…;type=…/ instead.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    structureTool: 'common', // the `name` of your structure tool
  },
})
exportboolean | { enabled?: boolean; formats?: Array<'csv' | 'json'> }default: true

When true (the default), both a 'Export CSV' and 'Export JSON' button appear in the controls bar. The export always reflects the current search query, status filter, type filter, and sort order — exactly the rows visible on screen. Set to false to hide all export controls. Pass an object to enable only specific formats.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',

    // Disable export entirely
    export: false,

    // — or — enable only CSV
    export: { formats: ['csv'] },

    // — or — enable only JSON
    export: { formats: ['json'] },
  },
})
compactStatsbooleandefault: false

When false (default), the dashboard renders the familiar 6-card grid showing Total, Avg Score, Excellent, Good, Fair, and Poor/Missing counts. When true, that grid is hidden and a compact pill row is shown inside the header instead — for example: '📋 12 · 🟢 Excellent: 4 · 🟡 Good: 3 · 🟠 Fair: 2 · 🔴 Poor/Missing: 3 · 📊 Avg: 65% · 🗂️ Types: 2'. Useful in dense dashboards or when vertical space is at a premium.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    compactStats: true,
  },
})
getDocumentBadge(doc) => { label, bgColor?, textColor?, fontSize? } | undefineddefault:

The function receives the full document object (including any custom fields your schema defines) and should return a badge descriptor or undefined to render nothing. bgColor and textColor accept any valid CSS colour value. Tip: use this to surface business-level metadata at a glance — for example, whether a page targets NHS or Private patients.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    getDocumentBadge: (doc) => {
      if (doc.services === 'NHS')
        return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
      if (doc.services === 'Private')
        return { label: 'Private', bgColor: '#fef3c7', textColor: '#92400e' }
    },
  },
})
docBadge(doc) => { label, bgColor?, textColor?, fontSize? } | undefineddefault: DEPRECATED getDocumentBadge

Renamed in v1.3.2 to getDocumentBadge. The old key still works for backwards compatibility but will be removed in a future major release.

sanity.config.ts
seofields({
  healthDashboard: {
    licenseKey: 'SEOF-XXXX-XXXX-XXXX',
    getDocumentBadge: (doc) => {   // ← replacement key
      if (doc.services === 'NHS')
        return { label: 'NHS', bgColor: '#e0f2fe', textColor: '#0369a1' }
    },
    // docBadge: (doc) => { ... }, // ← old key, still works
  },
})

Full Example

All options used together — mix and match what you need:

⚠️ Key renames in v1.3.2
  • display.typeColumn showTypeColumn
  • display.documentId showDocumentId
  • typeLabels typeDisplayLabels
  • docBadge getDocumentBadge

Old keys still work for backwards compatibility but are deprecated — the dashboard shows an amber banner listing every deprecated key in use, grouped by the release that introduced the rename, each with a direct link to the relevant changelog section. See the v1.3.2 changelog for full migration details.

✨ New in v1.5.2
  • Export CSV / JSON — download the currently filtered list via the export option. Both formats reflect the active search query, filters, and sort order.
  • Pagination & page-size selector — results are now paged (25 / 50 / 100 / 200 rows). The chosen page size persists in localStorage.
  • Saved filters — the last-used status filter and type filter are stored in localStorage and restored on next load.
  • Compact stat pills — set compactStats: true to replace the 6-card stats grid with a single row of inline pills.
✨ Also new in v1.3.2
  • Refresh button — a "Refresh" button in the dashboard header re-fetches documents without a full-page loading flash. The icon spins while the update runs and the button disables automatically during in-progress loads.
  • Non-string title warning — if a document's title is a Portable Text array (or any non-string value), an amber ⚠ badge appears in the Title column instead of a broken link. The badge prompts you to add "title": pt::text(title) to your query.groq projection.
sanity.config.ts
import { defineConfig } from 'sanity'
import seofields from 'sanity-plugin-seofields'


export default defineConfig({
  // ... your project config
  plugins: [
    seofields({
      healthDashboard: {
        // Required
        licenseKey: 'SEOF-XXXX-XXXX-XXXX',

        // Studio nav tab
        tool: {
          title: 'SEO Audit',       // tab label in Studio sidebar
          name: 'seo-audit',        // internal tool slug
        },

        // Dashboard page content & loading text
        content: {
          icon: '🔍',
          title: 'SEO Audit',
          description: 'Track SEO quality across published content.',
          loadingLicense: 'Checking your plan…',
          loadingDocuments: 'Fetching content, hang tight…',
          noDocuments: 'No pages with SEO fields yet.',
        },

        // Table columns
        showTypeColumn: true,      // replaces display.typeColumn
        showDocumentId: false,     // replaces display.documentId

        // Querying
        query: {
          types: ['post', 'page'],
          requireSeo: true,
          // groq: '*[_type == "post"] { _id, _type, title, seo, _updatedAt }',
        },

        // Type column labels & render mode
        typeDisplayLabels: {       // replaces typeLabels
          productDrug: 'Products',
          singleCondition: 'Condition',
        },
        typeColumnMode: 'badge', // 'badge' | 'text'

        // Use a different field as the display title per type
        titleField: {
          post: 'title',
          product: 'name',
          category: 'label',
        },

        // Custom badge next to document title
        getDocumentBadge: (doc) => {   // replaces docBadge
          if (doc.status === 'draft')
            return { label: 'Draft', bgColor: '#f3f4f6', textColor: '#6b7280' }
          if (doc.status === 'published')
            return { label: 'Published', bgColor: '#dcfce7', textColor: '#15803d' }
        },

        // Preview / demo mode — shows dummy data, no licence required
        previewMode: false,

        // Route document links to a specific structure tool (multi-tool setups)
        structureTool: 'desk', // omit if you only have one structure tool

        apiVersion: '2023-01-01',

        // Export — allow editors to download filtered data as CSV or JSON
        export: { formats: ['csv', 'json'] }, // or: export: false to disable

        // Compact stats — inline pills instead of 6-card grid (saves vertical space)
        compactStats: false,
      },
    }),
  ],
})

Was this page helpful?