Add Pagebuilder Block

/add-pagebuilder-blockCreated: 16 Sept 2025, 15:52Updated: 16 Sept 2025, 15:52
Variables
1
Goals
10
Path Groups
2
Nodes
13
Compact File Tree
Quick overview of planned files
Base Path
frontend
β”œβ”€ πŸ“ app
β”‚  └─ πŸ“ components
β”‚     β”œβ”€ πŸ“„ {{.PascalCaseBlockTypeSingular}}.tsx
β”‚     └─ πŸ“„ BlockRenderer.tsx
└─ πŸ“ sanity
   └─ πŸ“ lib
      └─ πŸ“„ queries.ts
Base Path
studio/src/schemaTypes
β”œβ”€ πŸ“ documents
β”‚  └─ πŸ“„ page.ts
β”œβ”€ πŸ“ objects
β”‚  β”œβ”€ πŸ“„ {{.KebabCaseBlockTypeSingular}}.ts
β”‚  └─ πŸ“„ pageBuilder.tsx
└─ πŸ“„ index.ts
Variables
Argument-driven inputs used by your generator
  • BlockTypeSingular
    Name your block type
    P1

    Sanity object `_type` used inside pageBuilder arrays.

    herotestimonialgalleryfeature
Ignored Patterns
Globs/paths skipped by the executor
No ignored patterns.
Goals
What this command is trying to accomplish
  1. Create React component for the new BlockType
    frontend/app/components/{{.PascalCaseBlockTypeSingular}}.tsx

    Scaffold a presentational component that renders heading, text, and an optional button using ResolvedLink, strongly typed from sanity.types.

  2. Register BlockType in BlockRenderer (import + mapping)
    frontend/app/components/BlockRenderer.tsx

    Wire the new component into the runtime renderer so Page Builder can display it.

    How-To Tips
    • Add an import for the new component below the React import (addMarkerBelowTarget).
    • Append a keyβ†’component pair to the Blocks map (addMarkerBelowTarget) using the block’s lowercased type as the key.
  3. Add BlockType selection to pageBuilderFields (GROQ)
    frontend/sanity/lib/queries.ts

    Include the block’s fields in the shared pageBuilder GROQ selection so it’s fetched for pages.

    How-To Tips
    • Within pageBuilderFields, add a new case `_type == "{{.KebabCaseBlockTypeSingular}}" => { ... }` near similar entries (addMarkerBelowTarget).
  4. Export pageBuilderFields and reuse in getPageQuery
    frontend/sanity/lib/queries.ts

    Ensure pageBuilderFields is a shared export and that getPageQuery references it to avoid drift.

    How-To Tips
    • Hoist pageBuilderFields into an exported fragment and update getPageQuery to interpolate it (replaceBetween) with requireAbsent guards.
  5. Export shared GROQ fragments if missing (linkFields, linkReference, postFields)
    frontend/sanity/lib/queries.ts

    Stabilize commonly reused fragments as exports so other queries can import them consistently.

    How-To Tips
    • Use replaceIfMissing to switch each first canonical `const` to `export const`, guarded with requireAbsent to avoid double-exports.
  6. Create Sanity object schema for BlockType
    studio/src/schemaTypes/objects/{{.KebabCaseBlockTypeSingular}}.ts

    Define the Studio object schema for the block with fields for heading, text, buttonText, and link, including a clean preview.

  7. Add BlockType to pageBuilder object (insert menu)
    studio/src/schemaTypes/objects/pageBuilder.tsx

    Expose the new block in the reusable pageBuilder array so editors can add it via the insert menu.

    How-To Tips
    • Insert `{type: '{{.CamelCaseBlockTypeSingular}}'},` inside the `of: [` list (addMarkerBelowTarget) keeping consistent ordering.
  8. Switch Page document to the reusable pageBuilder object
    studio/src/schemaTypes/documents/page.ts

    Standardize the Page schema to use the shared pageBuilder object instead of an inline array definition.

    How-To Tips
    • Replace the inline pageBuilder field with `{ name: 'pageBuilder', type: 'pageBuilder' }` (replaceBetween) to keep it idempotent.
  9. Register BlockType object in Studio schema index
    studio/src/schemaTypes/index.ts

    Import the new object schema and add it to the exported schemaTypes array.

    How-To Tips
    • Add the import for the object near other object imports (addMarkerBelowTarget).
    • Append the object to the Objects section of schemaTypes before the closing bracket (addMarkerAboveTarget).
  10. Ensure pageBuilder object is imported and listed in schemaTypes
    studio/src/schemaTypes/index.ts

    Guarantee the shared pageBuilder object is available in the Studio by importing and registering it once.

    How-To Tips
    • Import pageBuilder if absent and include it in schemaTypes (addMarkerAboveTarget with requireAbsent on both import and array entry).
File Tree
Detailed view with actions
Base Path
frontend
Detailed view
  • app
    Folder
    • components
      Folder
      • {{.PascalCaseBlockTypeSingular}}.tsx
        File
        View Code
        import {Suspense} from 'react'
        import ResolvedLink from '@/app/components/ResolvedLink'
        import { {{.PascalCaseBlockTypeSingular}} as {{.PascalCaseBlockTypeSingular}}Type  } from '@/sanity.types'
        
        type {{.PascalCaseBlockTypeSingular}}Props = {
          block: {{.PascalCaseBlockTypeSingular}}Type
        }
        
        export default function {{.PascalCaseBlockTypeSingular}}({block}: {{.PascalCaseBlockTypeSingular}}Props) {
          return (
            <section className="container my-12">
              <div className="bg-gray-50 border border-gray-100 rounded-2xl p-10 grid gap-6">
                {block?.heading && (
                  <h2 className="text-3xl font-bold tracking-tight text-black sm:text-4xl">{block.heading}</h2>
                )}
                {block?.text && <p className="text-lg leading-8 text-gray-600">{block.text}</p>}
                {block?.buttonText && block?.link && (
                  <Suspense fallback={null}>
                    <div className="flex items-center gap-x-6">
                      <ResolvedLink
                        link={block.link}
                        className="rounded-full flex gap-2 items-center bg-black hover:bg-blue focus:bg-blue py-3 px-6 text-white transition-colors duration-200"
                      >
                        {block.buttonText}
                      </ResolvedLink>
                    </div>
                  </Suspense>
                )}
              </div>
            </section>
          )
        }
        
      • BlockRenderer.tsx
        File β€’ Action File
        Actions
        1. Import Pagebuilder Block Component to BlockRenderer
          Behaviour: addMarkerBelowTarget
          Occurrence: last
          Target: import React from 'react'
          Content
          
          import {{.PascalCaseBlockTypeSingular}} from '@/app/components/{{.PascalCaseBlockTypeSingular}}'
        2. Adding Pagebuilder Block Components to "Blocks"
          Behaviour: addMarkerBelowTarget
          Occurrence: last
          Target: const Blocks: BlocksType = {
          Content
            {{.LowerCaseBlockTypeSingular}}: {{.PascalCaseBlockTypeSingular}},
          
        View Code
        import React from 'react'
        
        import Cta from '@/app/components/Cta'
        import Info from '@/app/components/InfoSection'
        import {dataAttr} from '@/sanity/lib/utils'
        
        
        type BlocksType = {
          [key: string]: React.FC<any>
        }
        
        type BlockType = {
          _type: string
          _key: string
        }
        
        type BlockProps = {
          index: number
          block: BlockType
          pageId: string
          pageType: string
        }
        
        const Blocks: BlocksType = {
          callToAction: Cta,
          infoSection: Info,
        }
        
        export default function BlockRenderer({block, index, pageId, pageType}: BlockProps) {
          if (typeof Blocks[block._type] !== 'undefined') {
            return (
              <div
                key={block._key}
                data-sanity={dataAttr({
                  id: pageId,
                  type: pageType,
                  path: `pageBuilder[_key==\"${block._key}\"]`,
                }).toString()}
              >
                {React.createElement(Blocks[block._type], {
                  key: block._key,
                  block: block,
                  index: index,
                })}
              </div>
            )}
          return (
            <div className="w-full bg-gray-100 text-center text-gray-500 p-20 rounded">
              A β€œ{block._type}” block hasn\'t been created
            </div>
          )
        }
        
  • sanity
    Folder
    • lib
      Folder
      • queries.ts
        File β€’ Action File
        Actions
        1. Setting "linkFields" to Export
          Behaviour: replaceIfMissing
          Occurrence: first
          Target: const linkFields = /* groq */ `
        2. Setting "postFields" to Export
          Behaviour: replaceIfMissing
          Occurrence: first
          Target: const postFields = /* groq */ `
        3. Setting "linkReference" to Export
          Behaviour: replaceIfMissing
          Occurrence: first
          Target: const linkReference = /* groq */ `
        4. Making "PageBuilder Fields" exportable
          Behaviour: replaceBetween
          Occurrence: first
        5. Adding "BlockType" to "pageBuilderFields"
          Behaviour: addMarkerBelowTarget
          Occurrence: first
          Target: export const pageBuilderFields = /* groq */ `
          Content
          _type == "{{.KebabCaseBlockTypeSingular}}" => {
          ...,
            },
        View Code
        //THIS IS AN INDEXER FILE 
        import {defineQuery} from 'next-sanity'
        
        export const settingsQuery = defineQuery(`*[_type == "settings"][0]`)
        
        export const postFields = /* groq */ `
          _id,
          "status": select(_originalId in path("drafts.**") => "draft", "published"),
          "title": coalesce(title, "Untitled"),
          "slug": slug.current,
          excerpt,
          coverImage,
          "date": coalesce(date, _updatedAt),
          "author": author->{firstName, lastName, picture},
        `
        
        export const linkReference = /* groq */ `
          _type == "link" => {
            "page": page->slug.current,
            "post": post->slug.current,
          }
        `
        
        export const linkFields = /* groq */ `
          link {
              ...,
              ${linkReference}
              }
        `
        
        export const pageBuilderFields = /* groq */ `
          ...,
          _type == "callToAction" => {
            ${linkFields},
          },
          _type == "infoSection" => {
            content[]{
              ...,
              titleDefs[]{
                ...,
                ${linkReference}
              }
            }
          }
        `
        
        export const getPageQuery = defineQuery(`
          *[_type == 'page' && slug.current == $slug][0]{
            _id,
            _type,
            name,
            slug,
            heading,
            subheading,
            "pageBuilder": pageBuilder[]{
              ${pageBuilderFields}
            },
          }
        `)
        
        
        
        export const sitemapData = defineQuery(`
          *[_type == "page" || _type == "post" && defined(slug.current)] | order(_type asc) {
            "slug": slug.current,
            _type,
            _updatedAt,
          }
        `)
        
        export const allPostsQuery = defineQuery(`
          *[_type == "post" && defined(slug.current)] | order(date desc, _updatedAt desc) {
            ${postFields}
          }
        `)
        
        export const morePostsQuery = defineQuery(`
          *[_type == "post" && _id != $skip && defined(slug.current)] | order(date desc, _updatedAt desc) [0...$limit] {
            ${postFields}
          }
        `)
        
        export const postQuery = defineQuery(`
          *[_type == "post" && slug.current == $slug] [0] {
            content[]{
            ...,
            titleDefs[]{
              ...,
              ${linkReference}
            }
          },
            ${postFields}
          }
        `)
        
        export const postPagesSlugs = defineQuery(`
          *[_type == "post" && defined(slug.current)]
          {"slug": slug.current}
        `)
        
        export const pagesSlugs = defineQuery(`
          *[_type == "page" && defined(slug.current)]
          {"slug": slug.current}
        `)
        
Base Path
studio/src/schemaTypes
Detailed view
  • documents
    Folder
    • page.ts
      File β€’ Action File
      Actions
      1. Use Standard Pagebuilder Object
        Behaviour: replaceBetween
        Occurrence: last
      View Code
      import {defineField, defineType} from 'sanity'
      import {DocumentIcon} from '@sanity/icons'
      
      /**
       * Page schema.  Define and edit the fields for the 'page' content type.
       * Learn more: https://www.sanity.io/docs/schema-types
       */
      
      export const page = defineType({
        name: 'page',
        title: 'Page',
        type: 'document',
        icon: DocumentIcon,
        fields: [
          defineField({
            name: 'name',
            title: 'Name',
            type: 'string',
            validation: (Rule) => Rule.required(),
          }),
      
          defineField({
            name: 'slug',
            title: 'Slug',
            type: 'slug',
            validation: (Rule) => Rule.required(),
            options: {
              source: 'name',
              maxLength: 96,
            },
          }),
          defineField({
            name: 'heading',
            title: 'Heading',
            type: 'string',
            validation: (Rule) => Rule.required(),
          }),
          defineField({
            name: 'subheading',
            title: 'Subheading',
            type: 'string',
          }),
          defineField({
            name: 'pageBuilder',
            title: 'Page builder',
            type: 'array',
            of: [{type: 'callToAction'}, {type: 'infoSection'}],
            options: {
              insertMenu: {
                // Configure the "Add Item" menu to display a thumbnail preview of the content type. https://www.sanity.io/docs/array-type#efb1fe03459d
                views: [
                  {
                    name: 'grid',
                    previewImageUrl: (schemaTypeName) =>
                      `/static/page-builder-thumbnails/${schemaTypeName}.webp`,
                  },
                ],
              },
            },
          }),
        ],
      })
      
  • objects
    Folder
    • {{.KebabCaseBlockTypeSingular}}.ts
      File
      View Code
      import {defineField, defineType} from 'sanity'
      import {DocumentIcon} from '@sanity/icons'
      
      export const {{.LowerCaseBlockTypeSingular}} = defineType({
        name: '{{.LowerCaseBlockTypeSingular}}',
        title: '{{.PascalCaseBlockTypeSingular}}',
        type: 'object',
        icon: DocumentIcon,
        fields: [
          defineField({ name: 'heading', title: 'Heading', type: 'string' }),
          defineField({ name: 'text', title: 'Text', type: 'text' }),
          defineField({ name: 'buttonText', title: 'Button text', type: 'string' }),
          defineField({ name: 'link', title: 'Button link', type: 'link' })
        ],
        preview: {
          select: { title: 'heading' },
          prepare({ title }) {
            return { title: title || '{{.PascalCaseBlockTypeSingular}}', subtitle: '{{.PascalCaseBlockTypeSingular}} block' }
          }
        }
      })
      
    • pageBuilder.tsx
      File β€’ Action File
      Actions
      1. Add BlockType to pageBuilder Schema
        Behaviour: addMarkerBelowTarget
        Occurrence: first
        Target: of: [
        Content
        {type: '{{.CamelCaseBlockTypeSingular}}'},
      View Code
      import { defineType } from "sanity";
      
      export const pageBuilder = defineType({
          name: 'pageBuilder',
          title: 'Page builder',
          type: 'array',
          of: [
            {type: 'callToAction'},
            {type: 'infoSection'}
            ],
          options: {
              insertMenu: {
                // Configure the "Add Item" menu to display a thumbnail preview of the content type. https://www.sanity.io/docs/array-type#efb1fe03459d
                views: [
                  {
                    name: 'grid',
                    previewImageUrl: (schemaTypeName) =>
                      `/static/page-builder-thumbnails/${schemaTypeName}.webp`,
                  },
                ],
              },
          }
      })
              
  • index.ts
    File β€’ Action File
    Actions
    1. Imporing Pagebuilder BlockType
      Behaviour: addMarkerBelowTarget
      Occurrence: last
      Target: import {blockContent} from './objects/blockContent'
      Content
      
      import { {{.LowerCaseBlockTypeSingular}} } from './objects/{{.KebabCaseBlockTypeSingular}}'
    2. OBJECT ARRAY ITEM
      Behaviour: addMarkerAboveTarget
      Occurrence: last
      Target: ]
      Content
      
        {{.LowerCaseBlockTypeSingular}},
    3. Add Pagebuilder Object to SchemaTypes
      Behaviour: addMarkerAboveTarget
      Occurrence: last
      Target: ]
      Content
      pageBuilder,
    4. Import PageBuilder Object to schemaTypes index
      Behaviour: addMarkerAboveTarget
      Occurrence: first
      Target: export const schemaTypes = [
      Content
      import {pageBuilder} from './objects/pageBuilder'
    View Code
    import {person} from './documents/person'
    import {page} from './documents/page'
    import {post} from './documents/post'
    import {callToAction} from './objects/callToAction'
    import {infoSection} from './objects/infoSection'
    import {settings} from './singletons/settings'
    import {link} from './objects/link'
    import {blockContent} from './objects/blockContent'
    
    // Export an array of all the schema types.  This is used in the Sanity Studio configuration. https://www.sanity.io/docs/schema-types
    
    export const schemaTypes = [
      // Singletons
      settings,
      // Documents
      page,
      post,
      person,
      // Objects
      blockContent,
      infoSection,
      callToAction,
      link,
    ]
    
Raw JSON
Debug view of the fetched document
{
  "_createdAt": "2025-09-16T15:52:29Z",
  "_id": "b0223c4c-63dc-4b14-b8e8-b85487273fff",
  "_rev": "2lI7L5yvGPDIkpqLKgygb2",
  "_system": {
    "base": {
      "id": "b0223c4c-63dc-4b14-b8e8-b85487273fff",
      "rev": "2lI7L5yvGPDIkpqLKgyfoG"
    }
  },
  "_type": "command-slug",
  "_updatedAt": "2025-09-16T15:52:46Z",
  "description": "Instantly scaffold a new Page Builder block across your Sanity Studio and Next.js frontendβ€”generating a typed React component, wiring it into BlockRenderer, extending the shared GROQ pageBuilderFields and getPageQuery, creating and registering the Studio object schema, exposing it in the reusable pageBuilder array/menu, and updating the Studio index.",
  "filePaths": [
    {
      "id": "path-1758028961857-11q1izg5e",
      "nodes": [
        {
          "_key": "1758028961857-f4n8ezmbp",
          "_type": "treeNode",
          "actionFile": false,
          "actions": [],
          "children": [
            {
              "_key": "1758028961857-0o2kx4ll9",
              "_type": "treeNode",
              "actionFile": false,
              "actions": [],
              "children": [
                {
                  "_key": "addblock-component-file",
                  "_type": "treeNode",
                  "actionFile": false,
                  "actions": [
                    {
                      "_key": "DZ9eudr4jMd24KPubMpyWj",
                      "logic": {},
                      "mark": ""
                    }
                  ],
                  "children": [],
                  "code": "import {Suspense} from 'react'\nimport ResolvedLink from '@/app/components/ResolvedLink'\nimport { {{.PascalCaseBlockTypeSingular}} as {{.PascalCaseBlockTypeSingular}}Type  } from '@/sanity.types'\n\ntype {{.PascalCaseBlockTypeSingular}}Props = {\n  block: {{.PascalCaseBlockTypeSingular}}Type\n}\n\nexport default function {{.PascalCaseBlockTypeSingular}}({block}: {{.PascalCaseBlockTypeSingular}}Props) {\n  return (\n    <section className=\"container my-12\">\n      <div className=\"bg-gray-50 border border-gray-100 rounded-2xl p-10 grid gap-6\">\n        {block?.heading && (\n          <h2 className=\"text-3xl font-bold tracking-tight text-black sm:text-4xl\">{block.heading}</h2>\n        )}\n        {block?.text && <p className=\"text-lg leading-8 text-gray-600\">{block.text}</p>}\n        {block?.buttonText && block?.link && (\n          <Suspense fallback={null}>\n            <div className=\"flex items-center gap-x-6\">\n              <ResolvedLink\n                link={block.link}\n                className=\"rounded-full flex gap-2 items-center bg-black hover:bg-blue focus:bg-blue py-3 px-6 text-white transition-colors duration-200\"\n              >\n                {block.buttonText}\n              </ResolvedLink>\n            </div>\n          </Suspense>\n        )}\n      </div>\n    </section>\n  )\n}\n",
                  "id": "file-frontend-block-component",
                  "name": "{{.PascalCaseBlockTypeSingular}}.tsx",
                  "nodeType": "file"
                },
                {
                  "_key": "addblock-renderer-patch",
                  "_type": "treeNode",
                  "actionFile": true,
                  "actions": [
                    {
                      "_key": "DZ9eudr4jMd24KPubMpybJ",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "\nimport {{.PascalCaseBlockTypeSingular}} from '@/app/components/{{.PascalCaseBlockTypeSingular}}'",
                        "mark": "IMPORT PAGEBUILDER BLOCK COMPONENTS",
                        "occurrence": "last",
                        "target": "import React from 'react'"
                      },
                      "title": "Import Pagebuilder Block Component to BlockRenderer"
                    },
                    {
                      "_key": "DZ9eudr4jMd24KPubMpyft",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "  {{.LowerCaseBlockTypeSingular}}: {{.PascalCaseBlockTypeSingular}},\n",
                        "mark": "PAGEBUILDER COMPONENTS",
                        "occurrence": "last",
                        "target": "const Blocks: BlocksType = {"
                      },
                      "title": "Adding Pagebuilder Block Components to \"Blocks\""
                    }
                  ],
                  "children": [],
                  "code": "import React from 'react'\n\nimport Cta from '@/app/components/Cta'\nimport Info from '@/app/components/InfoSection'\nimport {dataAttr} from '@/sanity/lib/utils'\n\n\ntype BlocksType = {\n  [key: string]: React.FC<any>\n}\n\ntype BlockType = {\n  _type: string\n  _key: string\n}\n\ntype BlockProps = {\n  index: number\n  block: BlockType\n  pageId: string\n  pageType: string\n}\n\nconst Blocks: BlocksType = {\n  callToAction: Cta,\n  infoSection: Info,\n}\n\nexport default function BlockRenderer({block, index, pageId, pageType}: BlockProps) {\n  if (typeof Blocks[block._type] !== 'undefined') {\n    return (\n      <div\n        key={block._key}\n        data-sanity={dataAttr({\n          id: pageId,\n          type: pageType,\n          path: `pageBuilder[_key==\\\"${block._key}\\\"]`,\n        }).toString()}\n      >\n        {React.createElement(Blocks[block._type], {\n          key: block._key,\n          block: block,\n          index: index,\n        })}\n      </div>\n    )}\n  return (\n    <div className=\"w-full bg-gray-100 text-center text-gray-500 p-20 rounded\">\n      A β€œ{block._type}” block hasn\\'t been created\n    </div>\n  )\n}\n",
                  "id": "file-frontend-block-renderer",
                  "name": "BlockRenderer.tsx",
                  "nodeType": "file"
                }
              ],
              "code": "",
              "id": "folder-1758028961857-0o2kx4ll9",
              "name": "components",
              "nodeType": "folder"
            }
          ],
          "code": "",
          "id": "folder-1758028961857-f4n8ezmbp",
          "name": "app",
          "nodeType": "folder"
        },
        {
          "_key": "1758028961857-8mopymmtk",
          "_type": "treeNode",
          "actionFile": false,
          "actions": [],
          "children": [
            {
              "_key": "1758028961857-0iq8le4bl",
              "_type": "treeNode",
              "actionFile": false,
              "actions": [],
              "children": [
                {
                  "_key": "1756919951450-hqircx415",
                  "_type": "treeNode",
                  "actionFile": true,
                  "actions": [
                    {
                      "_key": "DZ9eudr4jMd24KPubMpykT",
                      "logic": {
                        "behaviour": "replaceIfMissing",
                        "occurrence": "first",
                        "replacement": "export const linkFields = /* groq */ `",
                        "requireAbsent": "export const linkFields = /* groq */ `",
                        "target": "const linkFields = /* groq */ `"
                      },
                      "title": "Setting \"linkFields\" to Export"
                    },
                    {
                      "_key": "DZ9eudr4jMd24KPubMpyp3",
                      "logic": {
                        "behaviour": "replaceIfMissing",
                        "occurrence": "first",
                        "replacement": "export const postFields = /* groq */ `",
                        "requireAbsent": "export const postFields = /* groq */ `",
                        "target": "const postFields = /* groq */ `"
                      },
                      "title": "Setting \"postFields\" to Export"
                    },
                    {
                      "_key": "DZ9eudr4jMd24KPubMpytd",
                      "logic": {
                        "behaviour": "replaceIfMissing",
                        "occurrence": "first",
                        "replacement": "export const linkReference = /* groq */ `",
                        "requireAbsent": "export const linkReference = /* groq */ `",
                        "target": "const linkReference = /* groq */ `"
                      },
                      "title": "Setting \"linkReference\" to Export"
                    },
                    {
                      "_key": "DZ9eudr4jMd24KPubMpyyD",
                      "logic": {
                        "behaviour": "replaceBetween",
                        "occurrence": "first",
                        "replacement": "export const pageBuilderFields = /* groq */ `\n  ...,\n  _type == \"callToAction\" => {\n    ${linkFields},\n  },\n  _type == \"infoSection\" => {\n    content[]{\n      ...,\n      markDefs[]{\n        ...,\n        ${linkReference}\n      }\n    }\n  }\n`\n\nexport const getPageQuery = defineQuery(`\n  *[_type == 'page' && slug.current == $slug][0]{\n    _id,\n    _type,\n    name,\n    slug,\n    heading,\n    subheading,\n    \"pageBuilder\": pageBuilder[]{\n      ${pageBuilderFields}\n    },\n  }\n`)",
                        "requireAbsent": "export const pageBuilderFields = /* groq */ `",
                        "targetEnd": "`)",
                        "targetStart": "export const getPageQuery = defineQuery(`"
                      },
                      "title": "Making \"PageBuilder Fields\" exportable"
                    },
                    {
                      "_key": "DZ9eudr4jMd24KPubMpz2n",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "_type == \"{{.KebabCaseBlockTypeSingular}}\" => {\n...,\n  },",
                        "fallbackOnly": false,
                        "mark": "BLOCKTYPES FOR PAGEBUILDER",
                        "occurrence": "first",
                        "target": "export const pageBuilderFields = /* groq */ `"
                      },
                      "title": "Adding \"BlockType\" to \"pageBuilderFields\""
                    }
                  ],
                  "children": [],
                  "code": "//THIS IS AN INDEXER FILE \nimport {defineQuery} from 'next-sanity'\n\nexport const settingsQuery = defineQuery(`*[_type == \"settings\"][0]`)\n\nexport const postFields = /* groq */ `\n  _id,\n  \"status\": select(_originalId in path(\"drafts.**\") => \"draft\", \"published\"),\n  \"title\": coalesce(title, \"Untitled\"),\n  \"slug\": slug.current,\n  excerpt,\n  coverImage,\n  \"date\": coalesce(date, _updatedAt),\n  \"author\": author->{firstName, lastName, picture},\n`\n\nexport const linkReference = /* groq */ `\n  _type == \"link\" => {\n    \"page\": page->slug.current,\n    \"post\": post->slug.current,\n  }\n`\n\nexport const linkFields = /* groq */ `\n  link {\n      ...,\n      ${linkReference}\n      }\n`\n\nexport const pageBuilderFields = /* groq */ `\n  ...,\n  _type == \"callToAction\" => {\n    ${linkFields},\n  },\n  _type == \"infoSection\" => {\n    content[]{\n      ...,\n      titleDefs[]{\n        ...,\n        ${linkReference}\n      }\n    }\n  }\n`\n\nexport const getPageQuery = defineQuery(`\n  *[_type == 'page' && slug.current == $slug][0]{\n    _id,\n    _type,\n    name,\n    slug,\n    heading,\n    subheading,\n    \"pageBuilder\": pageBuilder[]{\n      ${pageBuilderFields}\n    },\n  }\n`)\n\n\n\nexport const sitemapData = defineQuery(`\n  *[_type == \"page\" || _type == \"post\" && defined(slug.current)] | order(_type asc) {\n    \"slug\": slug.current,\n    _type,\n    _updatedAt,\n  }\n`)\n\nexport const allPostsQuery = defineQuery(`\n  *[_type == \"post\" && defined(slug.current)] | order(date desc, _updatedAt desc) {\n    ${postFields}\n  }\n`)\n\nexport const morePostsQuery = defineQuery(`\n  *[_type == \"post\" && _id != $skip && defined(slug.current)] | order(date desc, _updatedAt desc) [0...$limit] {\n    ${postFields}\n  }\n`)\n\nexport const postQuery = defineQuery(`\n  *[_type == \"post\" && slug.current == $slug] [0] {\n    content[]{\n    ...,\n    titleDefs[]{\n      ...,\n      ${linkReference}\n    }\n  },\n    ${postFields}\n  }\n`)\n\nexport const postPagesSlugs = defineQuery(`\n  *[_type == \"post\" && defined(slug.current)]\n  {\"slug\": slug.current}\n`)\n\nexport const pagesSlugs = defineQuery(`\n  *[_type == \"page\" && defined(slug.current)]\n  {\"slug\": slug.current}\n`)\n",
                  "id": "file-1756919951450",
                  "name": "queries.ts",
                  "nodeType": "file"
                }
              ],
              "code": "",
              "id": "folder-1758028961857-0iq8le4bl",
              "name": "lib",
              "nodeType": "folder"
            }
          ],
          "code": "",
          "id": "folder-1758028961857-8mopymmtk",
          "name": "sanity",
          "nodeType": "folder"
        }
      ],
      "path": "frontend"
    },
    {
      "id": "studio-object-schema",
      "nodes": [
        {
          "_key": "1758030195496-luzwl8rkw",
          "_type": "treeNode",
          "actionFile": false,
          "actions": [],
          "children": [
            {
              "_key": "1758030210890-6twupqdki",
              "_type": "treeNode",
              "actionFile": true,
              "actions": [
                {
                  "logic": {
                    "behaviour": "replaceBetween",
                    "content": null,
                    "mark": null,
                    "occurrence": "last",
                    "target": null
                  },
                  "mark": "",
                  "title": "Use Standard Pagebuilder Object"
                }
              ],
              "children": [],
              "code": "import {defineField, defineType} from 'sanity'\nimport {DocumentIcon} from '@sanity/icons'\n\n/**\n * Page schema.  Define and edit the fields for the 'page' content type.\n * Learn more: https://www.sanity.io/docs/schema-types\n */\n\nexport const page = defineType({\n  name: 'page',\n  title: 'Page',\n  type: 'document',\n  icon: DocumentIcon,\n  fields: [\n    defineField({\n      name: 'name',\n      title: 'Name',\n      type: 'string',\n      validation: (Rule) => Rule.required(),\n    }),\n\n    defineField({\n      name: 'slug',\n      title: 'Slug',\n      type: 'slug',\n      validation: (Rule) => Rule.required(),\n      options: {\n        source: 'name',\n        maxLength: 96,\n      },\n    }),\n    defineField({\n      name: 'heading',\n      title: 'Heading',\n      type: 'string',\n      validation: (Rule) => Rule.required(),\n    }),\n    defineField({\n      name: 'subheading',\n      title: 'Subheading',\n      type: 'string',\n    }),\n    defineField({\n      name: 'pageBuilder',\n      title: 'Page builder',\n      type: 'array',\n      of: [{type: 'callToAction'}, {type: 'infoSection'}],\n      options: {\n        insertMenu: {\n          // Configure the \"Add Item\" menu to display a thumbnail preview of the content type. https://www.sanity.io/docs/array-type#efb1fe03459d\n          views: [\n            {\n              name: 'grid',\n              previewImageUrl: (schemaTypeName) =>\n                `/static/page-builder-thumbnails/${schemaTypeName}.webp`,\n            },\n          ],\n        },\n      },\n    }),\n  ],\n})\n",
              "id": "file-1758030210890",
              "name": "page.ts",
              "nodeType": "file"
            }
          ],
          "code": "",
          "id": "folder-1758030195496",
          "name": "documents",
          "nodeType": "folder"
        },
        {
          "_key": "1758029082445-f7khaqbw4",
          "_type": "treeNode",
          "actionFile": false,
          "actions": [],
          "children": [
            {
              "_key": "addblock-object-schema-file",
              "_type": "treeNode",
              "actionFile": false,
              "actions": [
                {
                  "logic": {
                    "behaviour": null,
                    "content": null,
                    "mark": null,
                    "occurrence": null,
                    "target": null
                  },
                  "mark": "",
                  "title": null
                }
              ],
              "children": [],
              "code": "import {defineField, defineType} from 'sanity'\nimport {DocumentIcon} from '@sanity/icons'\n\nexport const {{.LowerCaseBlockTypeSingular}} = defineType({\n  name: '{{.LowerCaseBlockTypeSingular}}',\n  title: '{{.PascalCaseBlockTypeSingular}}',\n  type: 'object',\n  icon: DocumentIcon,\n  fields: [\n    defineField({ name: 'heading', title: 'Heading', type: 'string' }),\n    defineField({ name: 'text', title: 'Text', type: 'text' }),\n    defineField({ name: 'buttonText', title: 'Button text', type: 'string' }),\n    defineField({ name: 'link', title: 'Button link', type: 'link' })\n  ],\n  preview: {\n    select: { title: 'heading' },\n    prepare({ title }) {\n      return { title: title || '{{.PascalCaseBlockTypeSingular}}', subtitle: '{{.PascalCaseBlockTypeSingular}} block' }\n    }\n  }\n})\n",
              "id": "file-studio-object-schema",
              "name": "{{.KebabCaseBlockTypeSingular}}.ts",
              "nodeType": "file"
            },
            {
              "_key": "1758029181288-0wejuus4u",
              "_type": "treeNode",
              "actionFile": true,
              "actions": [
                {
                  "logic": {
                    "behaviour": "addMarkerBelowTarget",
                    "content": "{type: '{{.CamelCaseBlockTypeSingular}}'},",
                    "mark": "PAGEBUILDER BLOCKS",
                    "occurrence": "first",
                    "target": "of: ["
                  },
                  "mark": "",
                  "title": "Add BlockType to pageBuilder Schema"
                }
              ],
              "children": [],
              "code": "import { defineType } from \"sanity\";\n\nexport const pageBuilder = defineType({\n    name: 'pageBuilder',\n    title: 'Page builder',\n    type: 'array',\n    of: [\n      {type: 'callToAction'},\n      {type: 'infoSection'}\n      ],\n    options: {\n        insertMenu: {\n          // Configure the \"Add Item\" menu to display a thumbnail preview of the content type. https://www.sanity.io/docs/array-type#efb1fe03459d\n          views: [\n            {\n              name: 'grid',\n              previewImageUrl: (schemaTypeName) =>\n                `/static/page-builder-thumbnails/${schemaTypeName}.webp`,\n            },\n          ],\n        },\n    }\n})\n        ",
              "id": "file-1758029181288-0wejuus4u",
              "name": "pageBuilder.tsx",
              "nodeType": "file"
            }
          ],
          "code": "",
          "id": "folder-1758029082445",
          "name": "objects",
          "nodeType": "folder"
        },
        {
          "_key": "20250903-studio-indexer-file",
          "_type": "treeNode",
          "actionFile": true,
          "actions": [
            {
              "logic": {
                "behaviour": "addMarkerBelowTarget",
                "content": "\nimport { {{.LowerCaseBlockTypeSingular}} } from './objects/{{.KebabCaseBlockTypeSingular}}'",
                "mark": "IMPORT PAGEBUILDER BLOCKS",
                "occurrence": "last",
                "target": "import {blockContent} from './objects/blockContent'"
              },
              "mark": null,
              "title": "Imporing Pagebuilder BlockType"
            },
            {
              "logic": {
                "behaviour": "addMarkerAboveTarget",
                "content": "\n  {{.LowerCaseBlockTypeSingular}},",
                "mark": "PAGEBUILDER BLOCKS",
                "occurrence": "last",
                "target": "]"
              },
              "mark": null,
              "title": "OBJECT ARRAY ITEM"
            },
            {
              "logic": {
                "behaviour": "addMarkerAboveTarget",
                "content": "pageBuilder,",
                "mark": null,
                "occurrence": "last",
                "target": "]"
              },
              "mark": "",
              "title": "Add Pagebuilder Object to SchemaTypes"
            },
            {
              "logic": {
                "behaviour": "addMarkerAboveTarget",
                "content": "import {pageBuilder} from './objects/pageBuilder'",
                "mark": "",
                "occurrence": "first",
                "target": "export const schemaTypes = ["
              },
              "mark": "",
              "title": "Import PageBuilder Object to schemaTypes index"
            }
          ],
          "children": [],
          "code": "import {person} from './documents/person'\nimport {page} from './documents/page'\nimport {post} from './documents/post'\nimport {callToAction} from './objects/callToAction'\nimport {infoSection} from './objects/infoSection'\nimport {settings} from './singletons/settings'\nimport {link} from './objects/link'\nimport {blockContent} from './objects/blockContent'\n\n// Export an array of all the schema types.  This is used in the Sanity Studio configuration. https://www.sanity.io/docs/schema-types\n\nexport const schemaTypes = [\n  // Singletons\n  settings,\n  // Documents\n  page,\n  post,\n  person,\n  // Objects\n  blockContent,\n  infoSection,\n  callToAction,\n  link,\n]\n",
          "id": "file-studio-indexer",
          "name": "index.ts",
          "nodeType": "file"
        }
      ],
      "path": "studio/src/schemaTypes"
    }
  ],
  "goals": [
    {
      "_key": "DZ9eudr4jMd24KPubMpxmv",
      "description": "Scaffold a presentational component that renders heading, text, and an optional button using ResolvedLink, strongly typed from sanity.types.",
      "fileHints": [
        "frontend/app/components/{{.PascalCaseBlockTypeSingular}}.tsx"
      ],
      "howToTips": [],
      "title": "Create React component for the new BlockType"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpxrV",
      "description": "Wire the new component into the runtime renderer so Page Builder can display it.",
      "fileHints": [
        "frontend/app/components/BlockRenderer.tsx"
      ],
      "howToTips": [
        "Add an import for the new component below the React import (addMarkerBelowTarget).",
        "Append a keyβ†’component pair to the Blocks map (addMarkerBelowTarget) using the block’s lowercased type as the key."
      ],
      "title": "Register BlockType in BlockRenderer (import + mapping)"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpxw5",
      "description": "Include the block’s fields in the shared pageBuilder GROQ selection so it’s fetched for pages.",
      "fileHints": [
        "frontend/sanity/lib/queries.ts"
      ],
      "howToTips": [
        "Within pageBuilderFields, add a new case `_type == \"{{.KebabCaseBlockTypeSingular}}\" => { ... }` near similar entries (addMarkerBelowTarget)."
      ],
      "title": "Add BlockType selection to pageBuilderFields (GROQ)"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpy0f",
      "description": "Ensure pageBuilderFields is a shared export and that getPageQuery references it to avoid drift.",
      "fileHints": [
        "frontend/sanity/lib/queries.ts"
      ],
      "howToTips": [
        "Hoist pageBuilderFields into an exported fragment and update getPageQuery to interpolate it (replaceBetween) with requireAbsent guards."
      ],
      "title": "Export pageBuilderFields and reuse in getPageQuery"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpy5F",
      "description": "Stabilize commonly reused fragments as exports so other queries can import them consistently.",
      "fileHints": [
        "frontend/sanity/lib/queries.ts"
      ],
      "howToTips": [
        "Use replaceIfMissing to switch each first canonical `const` to `export const`, guarded with requireAbsent to avoid double-exports."
      ],
      "title": "Export shared GROQ fragments if missing (linkFields, linkReference, postFields)"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpy9p",
      "description": "Define the Studio object schema for the block with fields for heading, text, buttonText, and link, including a clean preview.",
      "fileHints": [
        "studio/src/schemaTypes/objects/{{.KebabCaseBlockTypeSingular}}.ts"
      ],
      "howToTips": [],
      "title": "Create Sanity object schema for BlockType"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpyEP",
      "description": "Expose the new block in the reusable pageBuilder array so editors can add it via the insert menu.",
      "fileHints": [
        "studio/src/schemaTypes/objects/pageBuilder.tsx"
      ],
      "howToTips": [
        "Insert `{type: '{{.CamelCaseBlockTypeSingular}}'},` inside the `of: [` list (addMarkerBelowTarget) keeping consistent ordering."
      ],
      "title": "Add BlockType to pageBuilder object (insert menu)"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpyIz",
      "description": "Standardize the Page schema to use the shared pageBuilder object instead of an inline array definition.",
      "fileHints": [
        "studio/src/schemaTypes/documents/page.ts"
      ],
      "howToTips": [
        "Replace the inline pageBuilder field with `{ name: 'pageBuilder', type: 'pageBuilder' }` (replaceBetween) to keep it idempotent."
      ],
      "title": "Switch Page document to the reusable pageBuilder object"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpyNZ",
      "description": "Import the new object schema and add it to the exported schemaTypes array.",
      "fileHints": [
        "studio/src/schemaTypes/index.ts"
      ],
      "howToTips": [
        "Add the import for the object near other object imports (addMarkerBelowTarget).",
        "Append the object to the Objects section of schemaTypes before the closing bracket (addMarkerAboveTarget)."
      ],
      "title": "Register BlockType object in Studio schema index"
    },
    {
      "_key": "DZ9eudr4jMd24KPubMpyS9",
      "description": "Guarantee the shared pageBuilder object is available in the Studio by importing and registering it once.",
      "fileHints": [
        "studio/src/schemaTypes/index.ts"
      ],
      "howToTips": [
        "Import pageBuilder if absent and include it in schemaTypes (addMarkerAboveTarget with requireAbsent on both import and array entry)."
      ],
      "title": "Ensure pageBuilder object is imported and listed in schemaTypes"
    }
  ],
  "ignoredPatterns": [],
  "slug": {
    "_type": "slug",
    "current": "add-pagebuilder-block"
  },
  "slugCurrent": "add-pagebuilder-block",
  "title": "Add Pagebuilder Block",
  "variables": [
    {
      "_type": "variableDefinition",
      "description": "Sanity object `_type` used inside pageBuilder arrays.",
      "examples": [
        "hero",
        "testimonial",
        "gallery",
        "feature"
      ],
      "name": "BlockTypeSingular",
      "priority": 1,
      "title": "Name your block type"
    }
  ]
}