🔮 Sanity Create is here. Writing is reinvented. Try now, no developer setup

Website Meta Document

By Andre Clark

Manage Meta tags, openGraph, and locale Data for your site within Sanity Studio

siteMeta.ts

import { openGraph } from "../objects";

export default {
  type: "document",
  name: "siteMeta",
  title: "Site Configuration",
  fieldsets: [
    { name: "google", title: "Google Analytics" },
  ],
  groups: [
    {
      name: "meta",
      title: "Site Info",
      default: true
    },
    {
      name: "og",
      title: "Social Share Info",
    },
    {
      name: "manifest",
      title: "Web App Settings",
      hidden: ({ document }: {  document: {
        [key: string]: never;
      }}): boolean => !(document.isPwa)
    },
    {
      name: "google",
      title: "Google Config",
      hidden: ({ document }: {  document: {
        [key: string]: never;
      }}): boolean => !(document.isGoogleAnalyticsEnabled)
    },
  ],
  fields: [
    {
    type: 'string',
    name: 'site_name',
    title: 'Site Name',
    group: ['og', 'meta'],
    // fieldset: "optional"
  },
  {
    type: "text",
    name: "ogDescription",
    title: "Social Share Description",
    group: ['og', 'meta']
  },
  {
    type: 'url',
    title: 'URL',
    name: 'url',
    description: 'Most likely either the url of the page or its canonical url',
    validation: (Rule: Rule) => Rule.required(),
    group: ['og', 'meta'],
    // fieldset: "basic"
  },
  {
    type: 'string',
    title: 'Page Title',
    name: 'ogTitle',
    description:
      'Set the title Open Graph should use. In most situations, this should be different from the value of the title prop',
    validation: (Rule: Rule) => Rule.required(),
    // fieldset: "basic"
  },
  {
    type: 'image',
    title: 'Image',
    name: 'ogImage',
    description:
      'URL of the image that should be used in social media previews. If you define this, you must define two other OG basic properties as well: title and type.',
    validation: (Rule: Rule) => Rule.required(),
    group: ['og'],
    // fieldset: "basic"
  },
    {
      type: "text",
      name: "description",
      title: "Describe This Site",
      group: ["meta", "og"]
    },
    {
      type: "boolean",
      name: "isPwa",
      title: "should this site be installable like an app?",
      group: [
        "meta", "manifest"
      ],
      initialValue: false,
      options: {
        layout: "checkbox"
      }
    },
    {
      type: "boolean",
      name: "isGoogleAnalyticsEnabled",
      title: "Enable Google Analytics?",
      group: ["meta", "google"],
      initialValue: false,
      options: {
        layout: "checkbox"
      }
    },
    {
      type: "string",
      name: "googleanalyticsId",
      title: "Google Analytics ID",
      fieldset: "google",
      group: ["meta", "google"],
    },
    {
      type: "string",
      name: "googleSiteVerificationId",
      title: "Google site Verification ID",
      fieldset: "google",
      group: ["meta", "google"],
    },
    {
      type: "manifest",
      title: "Web App Features",
      name: "manifest",
      group: "manifest"
    }
  ],
  initialValue: {
    isPwa: false,
    isGoogleAnalyticsEnabled: false,
  }
};

openGraph.ts

import type { Rule } from "@sanity/types";
import locale from "./locale";

export default {
  name: "openGraph",
  title: "Social Share Config",
  type: "object",
  fields: [
  {
    type: 'string',
    name: 'site_name',
    title: 'Site Name',
    group: ['og', 'meta'],
    // fieldset: "optional"
  },
  {
    type: "text",
    name: "ogDescription",
    title: "Social Share Description",
    group: ['og', 'meta']
  },
  {
    type: 'url',
    title: 'URL',
    name: 'url',
    description: 'Most likely either the url of the page or its canonical url',
    validation: (Rule: Rule) => Rule.required(),
    group: ['og', 'meta'],
    // fieldset: "basic"
  },
  {
    type: 'string',
    title: 'Page Title',
    name: 'ogTitle',
    description:
      'Set the title Open Graph should use. In most situations, this should be different from the value of the title prop',
    validation: (Rule: Rule) => Rule.required(),
    // fieldset: "basic"
  },
  {
    type: 'image',
    title: 'Image',
    name: 'ogImage',
    description:
      'URL of the image that should be used in social media previews. If you define this, you must define two other OG basic properties as well: title and type.',
    validation: (Rule: Rule) => Rule.required(),
    group: ['og'],
    // fieldset: "basic"
  },
  locale
]
}

locale.ts

const LOCALES: Locales = [
  {
    title: "American English",
    value: "en-US",
  },
  {
    title: "British English",
    value: "en-GB",
  },
  {
    title: "French",
    value: "fr-FR",
  },
];
const DISPLAYMODES: DisplayModes =  [
  'browser',
  'fullscreen',
  'minimal-ui',
  'standalone',
]
export default {
  type: "string",
  name: "locale",
  title: "Language",
  description:
    "used for informing the browser the site's language. Should be a valid bcp47 language code like en, 'en-US', 'no' or 'nb-NO'",
  options: {
    list: LOCALES
  },
  initialValue: LOCALES[0].value,
  group: ['meta', 'og'],
}

getSiteMetaData.ts

const querySiteMeta = `
*[_type=="siteMeta"][0] {
  title,
  description,
  "canonical": url,
  isGoogleAnalyticsEnabled,
  isPwa,
  manifest {
    ...,
    "background_color": background_color.hex,
    "theme_color": theme_color.hex
  },
  "openGraph": {
    "basic": { 
    title,
    url,
    "image": image.asset->url
    },
    "optional": {
      locale,
      site_name,
      description
    }
  }
}
`


export default async function getSiteMeta(
  query: string = querySiteMeta,
  client: SanityClientLike,
  mutation = "fetch"
):Promise<Site> {
  const site: Site = await client[mutation](query)
  return site
}

siteMeta.ts is ideally used as a singleton document within the settings/configuration section of your studio.

openGraph.ts and locale.ts can also be imported as individual objects/ fields to add anywhere into your schemas.

the siteMeta document can be fetched/queried with getSiteMeta.ts
the function also includes the groq query.

also available as a package

npm i sanity-meta

improvements and contributions welcome

github repository: https://github.com/AndreBClark/cosmic-schemas/tree/main/schemas/sanity-meta

Contributor