Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.siftly.ai/llms.txt

Use this file to discover all available pages before exploring further.

Supported CMS platforms including Sanity

Overview

This guide is for Sanity users who want to publish Siftly-generated GEO content and apply SEO/GEO field recommendations (meta tags, JSON-LD, keywords) directly to their Sanity schema. After following it, you’ll have a complete SEO schema, working field mapping, and frontend rendering code for all GEO-critical fields. Difficulty: ❌ Requires developer — Sanity is headless; you build the schema and the frontend rendering yourself. Sanity is a headless CMS built around structured content and a powerful API. The Siftly–Sanity integration pushes content directly to your Sanity Content Lake as new documents, using the unified 3-tier field mapping system and Sanity’s GROQ-powered API under the hood. This integration is ideal for teams using Sanity to power content-heavy websites, documentation, or multi-channel publishing pipelines.

Prerequisites

Before connecting, you’ll need:
  • A Sanity project with a schema that includes a document type for your content (e.g., post, article)
  • A Sanity API token with Editor or Deploy Studio write permissions
  • Your Sanity Project ID and Dataset name

Step 1: Get your Sanity credentials

Project ID and Dataset

  1. Log in to sanity.io/manage
  2. Select your project
  3. Copy the Project ID from the project overview page
  4. Note your Dataset name (usually production)

API Token

  1. In Sanity Manage, go to API -> Tokens
  2. Click Add API Token
  3. Give it a descriptive name (e.g., “Siftly Integration”)
  4. Set the permission level to Editor
  5. Click Save and copy the token
Sanity API tokens with Editor permissions can create, modify, and delete documents, and upload assets (images) to the Content Lake. Store the token securely and restrict it to the minimum dataset it needs access to.
The Editor permission level includes asset upload access. Siftly uploads hero images and inline content images to the Sanity Assets API automatically at publish time — no additional permissions are needed.

Step 2: Connect Sanity in Siftly

  1. In Siftly, go to Settings -> Integrations
  2. Click Connect next to Sanity
  3. Enter your:
    • Project ID
    • Dataset name
    • API Token
  4. Click Verify Connection
Siftly will connect to your Content Lake and validate the credentials. If successful, you move to the schema mapping step.

Step 3: Schema Mapping

After credentials are validated, Siftly discovers your schema and presents the mapping interface.

Document Type Selection

Choose the Sanity document type that Siftly should create when publishing. Siftly automatically discovers all document types in your schema and presents them as a dropdown. You can also enter a custom type name. When you select a type, Siftly fetches a sample document via GROQ and discovers all available fields — including nested fields inside objects (e.g., seo.metaTitle, schemaOrg.datePublished).

Field Mapping

Siftly uses a 3-tier field mapping system. Select fields from the dropdown (discovered from your schema) for each field you want Siftly to populate.

Required Fields

Siftly FieldTypical Sanity FieldNotes
TitletitlePlain text
SlugslugSanity handles the {_type: "slug", current: "..."} format automatically
BodybodyRich text — maps to Portable Text or HTML embed (see below)

Optional Fields

Leave any of these blank to skip them. When mapped, they are auto-populated from your content data.
Siftly FieldTypical Sanity FieldNotes
Meta Titleseo.metaTitleDot-notation supported for nested objects
Meta Descriptionseo.metaDescriptionDot-notation supported for nested objects
KeywordstagsArray of strings or comma-separated string
CategoriescategoryString value
JSON-LDschemaString or JSON — controlled by the type toggle
Word CountwordCountNumber field
Hero ImagefeaturedImageImage field

JSON-LD Type Toggle

When a JSON-LD field is mapped, a text/json toggle appears:
  • json (default): Sends as a parsed JSON object (with @ prefixes stripped for Sanity compatibility)
  • text: Sends as a serialized JSON string
Choose based on your Sanity schema’s field type for this field.

Nested Field Discovery

Siftly recursively discovers fields inside nested objects up to 3 levels deep. For example, an SEO settings object with sub-fields appears as:
  • seo.metaTitle (String)
  • seo.metaDescription (String)
  • seo.ogImage (Image)
  • schemaOrg.datePublished (String)
These are shown in the dropdown with > separators: SEO > Meta Title.

Body Format

Toggle Store body as HTML embed to control how body content is stored:
  • Off (default): Content is converted to Portable Text blocks (headings, paragraphs, lists, links)
  • On: Content is stored as an htmlEmbed block containing the full HTML. Your schema needs an htmlEmbed type defined in the body array, and your frontend should render it with proper styling.

Portable Text

When HTML embed is off, Siftly automatically converts generated HTML content to valid Portable Text:
  • Headings (H2, H3, H4)
  • Bold, italic, and inline code
  • Ordered and unordered lists
  • Hyperlinks (including internal links generated by Siftly)
  • Block quotes
Custom Portable Text block types (e.g., call-out cards, custom embeds) are not supported by the auto-converter.

Custom Fields

Add extra fields your Sanity document type requires that aren’t part of Siftly’s standard content schema.

Field Types

TypeDescriptionExample
StringPlain textAuthor name, subtitle
NumberNumeric valuePriority, reading time
BooleanTrue/falseFeatured post, comments enabled
JSONStructured objectSEO config, social media metadata
SelectPick from predefined optionsCategory, status, content type

Select Options with Separate Labels and Values

When adding a Select custom field, you define the available options in a textarea. Each option can have a separate value (sent to CMS) and label (shown in the dropdown):
value:label
Example — Labels differ from values:
1:Health
2:Tech
3:Food
4:Fashion
The dropdown shows Health, Tech, etc., but Siftly sends 1, 2, etc. to the CMS API. Example — Labels same as values:
food
tech
health
fashion
When no : is present, the option text is used as both the label and value. This is backward-compatible with existing configurations.
Use the value:label format when your CMS expects specific IDs, slugs, or enum values that differ from the human-readable names shown to content editors.

Default Values

Set a default value when adding a custom field. This value pre-fills at publish time — you can override it for each post.

Use Existing Field (Source Fields)

Instead of setting a manual default, link a custom field to an existing content source. The value is auto-populated at publish time — no manual input needed. Available sources:
  • Title, Slug, Meta Title, Meta Description, Excerpt
  • Keywords, Categories, Word Count
  • JSON-LD, Hero Image
  • Date Published, Date Modified (extracted from the JSON-LD graph)
This is especially useful for nested fields. For example:
  • seo.metaTitle -> Use Existing: Meta Title
  • seo.wordCount -> Use Existing: Word Count
  • schemaOrg.dateModified -> Use Existing: Date Modified

Editing Custom Fields

Click the pencil icon on any existing custom field to edit its configuration (field path, label, type, source, default value).

Auto-Populated Fields

Siftly automatically sets these fields on every document if not already provided by a custom field mapping:
FieldValue
status"published" or "draft" based on your publish choice
publishedAtISO timestamp (only for published, not draft)

Publishing

Draft vs Published

When publishing, choose between:
  • Draft — the document is created with drafts. prefix ID, visible only in Studio
  • Published — the document is created as a live document with publishedAt set

Custom Fields at Publish Time

Custom fields with Use Existing Field set show as “Auto-populated from [source]” in the publish dialog — no input needed. Manual custom fields appear with their default values pre-filled and editable.

Republishing

Siftly uses createOrReplace for idempotent updates. Republishing the same content updates the existing Sanity document (same ID: siftly-{content_id}). Publishing also deletes any stray draft/published counterpart to avoid duplicates.

GROQ Previewing

After publishing, verify the document in Sanity Studio’s Vision tool:
*[_type == "post" && slug.current == "your-slug"][0] {
  title,
  slug,
  body,
  publishedAt,
  status
}

Webhooks (optional)

For teams with automated publishing workflows, Siftly can call a Sanity webhook after creating a document — for example, to trigger a re-build of your Next.js or Remix frontend.
  1. In Sanity Manage, go to API → Webhooks → Add Webhook
  2. Set the URL to your frontend’s webhook endpoint
  3. Copy the webhook URL
  4. In Siftly → Settings → Integrations → Sanity → Advanced, paste the webhook URL in Post-publish webhook
Siftly will call this URL with a POST request after each successful publish.

Where to apply Siftly’s recommendations on Sanity

Sanity is fully headless — you define the schema, Siftly pushes data into it, and your frontend renders it as HTML meta tags and structured data. Below is the complete recommended schema and rendering setup. Create a reusable seo object type that you can add to any document type:
import { defineType, defineField } from 'sanity';

export const seo = defineType({
  name: 'seo',
  title: 'SEO & GEO Fields',
  type: 'object',
  fields: [
    defineField({
      name: 'metaTitle',
      title: 'Meta Title',
      type: 'string',
      description: 'Page title for search engines and AI citations (50-60 characters ideal)',
      validation: (Rule) => Rule.max(70).warning('Keep under 60 characters for best results'),
    }),
    defineField({
      name: 'metaDescription',
      title: 'Meta Description',
      type: 'text',
      rows: 3,
      description: 'Page description for search engines and social sharing (150-160 characters ideal)',
      validation: (Rule) => Rule.max(160).warning('Keep under 160 characters'),
    }),
    defineField({
      name: 'ogImage',
      title: 'Open Graph Image',
      type: 'image',
      description: 'Social sharing image (1200x630px recommended)',
      options: { hotspot: true },
    }),
    defineField({
      name: 'canonicalUrl',
      title: 'Canonical URL',
      type: 'url',
      description: 'Override canonical URL (leave empty to use default page URL)',
    }),
    defineField({
      name: 'keywords',
      title: 'Keywords',
      type: 'array',
      of: [{ type: 'string' }],
      description: 'Target keywords for this content',
      options: { layout: 'tags' },
    }),
    defineField({
      name: 'jsonLd',
      title: 'JSON-LD Structured Data',
      type: 'text',
      rows: 10,
      description: 'Paste Siftly\'s JSON-LD recommendation here. Must be valid JSON.',
    }),
  ],
});

Meta Title

Schema location: seo.metaTitle Siftly mapping: Map Siftly’s Meta Title field to seo.metaTitle in your integration settings. Frontend rendering (Next.js App Router):
// app/blog/[slug]/page.tsx
import { client } from '@/sanity/lib/client';

export async function generateMetadata({ params }) {
  const post = await client.fetch(
    `*[_type == "post" && slug.current == $slug][0]{
      title,
      "metaTitle": seo.metaTitle,
      "metaDescription": seo.metaDescription,
      "ogImage": seo.ogImage.asset->url
    }`,
    { slug: params.slug }
  );

  return {
    title: post.metaTitle || post.title,
    description: post.metaDescription,
    openGraph: {
      title: post.metaTitle || post.title,
      description: post.metaDescription,
      images: post.ogImage ? [{ url: post.ogImage }] : [],
    },
  };
}

Meta Description

Schema location: seo.metaDescription Siftly mapping: Map Siftly’s Meta Description field to seo.metaDescription. Rendered via the same generateMetadata function above.

Open Graph Image

Schema location: seo.ogImage (Sanity image type with asset reference)
Siftly does not push image uploads to Sanity. After publishing, upload your OG image to the document in Sanity Studio. Alternatively, use a text field (seo.ogImageUrl) for external image URLs.

Canonical URL

Schema location: seo.canonicalUrl Frontend rendering:
export async function generateMetadata({ params }) {
  const post = await client.fetch(`*[_type == "post" && slug.current == $slug][0]{
    "canonicalUrl": seo.canonicalUrl,
    "slug": slug.current
  }`, { slug: params.slug });

  return {
    alternates: {
      canonical: post.canonicalUrl || `/blog/${post.slug}`,
    },
  };
}

Tags & Categories

Schema location: tags (array of strings at document root) Siftly mapping: Map Siftly’s Keywords field to tags. Tags are rendered in your frontend for navigation, filtering, or as meta keywords:
<meta name="keywords" content={post.tags?.join(', ')} />

JSON-LD Structured Data

Schema location: seo.jsonLd (text field storing raw JSON) Siftly mapping: Map Siftly’s JSON-LD field to seo.jsonLd (or jsonLd at root). Frontend rendering:
// app/blog/[slug]/page.tsx
export default async function PostPage({ params }) {
  const post = await client.fetch(
    `*[_type == "post" && slug.current == $slug][0]{
      title,
      body,
      "jsonLd": seo.jsonLd
    }`,
    { slug: params.slug }
  );

  return (
    <>
      {post.jsonLd && (
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: post.jsonLd }}
        />
      )}
      <article>
        {/* Render post.body with PortableText */}
      </article>
    </>
  );
}
For maximum GEO impact, Siftly’s JSON-LD recommendations typically include Article, FAQ, or HowTo schema. Store the entire JSON-LD object in the seo.jsonLd field and render it verbatim in your frontend’s <head>.

Keywords

Schema location: seo.keywords (array of strings) or tags at the document root. Siftly pushes keywords as an array. Your frontend renders them in a <meta name="keywords"> tag or uses them for internal search/filtering.

Pushing Siftly recommendations automatically

1

Generate content in Siftly

Siftly produces your article plus meta title, meta description, keywords, JSON-LD, and word count.
2

Click Publish → Sanity

Select your document type. Choose Draft or Published status.
3

All fields pushed in one API call

Siftly creates a new document in your Content Lake with all mapped fields populated — body (as Portable Text), SEO object fields, tags, and JSON-LD.
4

Frontend picks up new content

Your frontend queries the new document via GROQ and renders all meta tags, JSON-LD, and content from the populated fields.

GROQ query for all SEO fields

Use this query to fetch everything your frontend needs for a complete GEO-optimized page:
*[_type == "post" && slug.current == $slug][0] {
  title,
  "slug": slug.current,
  body,
  publishedAt,
  tags,
  wordCount,
  "seo": {
    "metaTitle": seo.metaTitle,
    "metaDescription": seo.metaDescription,
    "ogImage": seo.ogImage.asset->url,
    "canonicalUrl": seo.canonicalUrl,
    "keywords": seo.keywords,
    "jsonLd": seo.jsonLd
  }
}

Platform-specific quirks & limitations

Nothing renders automatically. Sanity stores structured content — it does NOT generate any HTML, meta tags, or JSON-LD output. Your frontend is 100% responsible for reading these fields and rendering them as proper HTML <meta> tags and <script type="application/ld+json"> blocks.
Portable Text conversion: Siftly converts HTML to Portable Text automatically when publishing. Standard formatting (headings, lists, links, bold, italic, blockquotes) is preserved. Custom block types in your schema are NOT supported by the auto-converter.
  • Image fields: Siftly cannot upload images to Sanity’s asset pipeline. Use text URL fields for OG images or upload images manually in Sanity Studio after publishing.
  • References: If your schema uses references (e.g., author → Author document), Siftly cannot create or link references. Keep these fields optional or set them manually.
  • Array of objects: Complex nested arrays (e.g., FAQ items as an array of {question, answer} objects) cannot be mapped from Siftly’s flat field structure. Store FAQ schema as JSON-LD in the seo.jsonLd text field instead.
  • Real-time preview: Sanity’s real-time preview (via next-sanity or @sanity/preview-kit) will show new Siftly-published documents immediately in draft mode without a page refresh.
  • Dataset isolation: If you use multiple datasets (e.g., production and staging), make sure Siftly is connected to the correct dataset for your publishing workflow.

Validating the setup

After publishing your first document from Siftly:
1

Check in Sanity Studio

Open Sanity Studio → find your new document → verify all fields (title, body, SEO object, tags, JSON-LD) are populated.
2

Run your GROQ query

Open Vision in Sanity Studio and run the GROQ query above with your test slug. Confirm all SEO fields return data.
3

View the live frontend page source

Open the rendered page. Right-click → View Page Source. Search for:
  • <title> — should contain your meta title from Sanity
  • <meta name="description" — should contain your meta description
  • <script type="application/ld+json"> — should contain your JSON-LD
4

Run Google Rich Results Test

Go to Google Rich Results Test and paste your URL. Confirm structured data is detected and valid.
5

Run Schema.org Validator

Go to Schema.org Validator for additional validation.

Difficulty & setup recap

Sanity Setup Summary

AspectRating
Initial setup❌ Developer required — schema design + frontend code
Applying SEO fields❌ Must build schema object and frontend rendering
JSON-LD❌ Stored as text; frontend must render <script> tag explicitly
Ongoing editing✅ Easy — publish from Siftly, review in Sanity Studio
Developer needed?Yes — for schema and frontend. Not for ongoing content publishing.
GEO control level✅ Maximum — you control every field and rendering detail

CMS Integrations Overview

Compare all supported platforms and the unified field mapping system.

Quickstart Guide

Set up your brand and run your first GEO analysis.

Content Generation

How Siftly generates GEO-optimized content and recommendations.

Choosing a CMS for AI Visibility

Our guide to picking the right CMS for GEO optimization.

Troubleshooting

Your API token doesn’t have write access to the selected dataset. Regenerate the token with Editor permissions in Sanity Manage.
Check your field mapping. Fields with Use Existing Field require the source data to exist in your content. Verify the mapping dropdown matches your actual schema field names.
Siftly automatically wraps slugs in Sanity’s {_type: "slug", current: "your-slug"} format. Just map to the slug field name.
When JSON-LD type is set to json, the @ prefixes are stripped (@type becomes type). Your Sanity schema needs matching sub-fields (e.g., context, graph) defined on the object, or use text mode to store as a string.
This can happen from failed previous attempts. Republish the content — Siftly now automatically deletes the opposite version when publishing.