Add Portable Text Block

/add-portable-text-blockCreated: 19 Sept 2025, 11:46Updated: 19 Sept 2025, 16:08
Variables
1
Goals
1
Path Groups
2
Nodes
13
Compact File Tree
Quick overview of planned files
Base Path
frontend
โ””โ”€ ๐Ÿ“ app
   โ””โ”€ ๐Ÿ“ components
      โ”œโ”€ ๐Ÿ“ portable-text-components
      โ”‚  โ””โ”€ ๐Ÿ“ portable-blocks
      โ”‚     โ”œโ”€ ๐Ÿ“„ Block{{.PascalCaseBlockTitle}}.tsx
      โ”‚     โ””โ”€ ๐Ÿ“„ index.ts
      โ””โ”€ ๐Ÿ“„ PortableText.tsx
Base Path
/studio/src
โ””โ”€ ๐Ÿ“ schemaTypes
   โ””โ”€ ๐Ÿ“ objects
      โ”œโ”€ ๐Ÿ“ portable-text-components
      โ”‚  โ””โ”€ ๐Ÿ“ portable-text-blocks
      โ”‚     โ””โ”€ ๐Ÿ“„ Block{{.PascalCaseBlockTitle}}Preview.tsx
      โ””โ”€ ๐Ÿ“„ blockContent.tsx
Variables
Argument-driven inputs used by your generator
  • BlockTitle
    BlockTitle
    P1

    Human-friendly label shown in the Studio style dropdown.

    Custom HeadingLead ParagraphIntroCallout
Ignored Patterns
Globs/paths skipped by the executor
No ignored patterns.
Goals
What this command is trying to accomplish
  1. Wire a custom Portable Text block style across Studio & Frontend

    Adds a reusable block style to the Studio editor (with optional preview component) and renders it on the frontend by extending the PortableText block map.

File Tree
Detailed view with actions
Base Path
frontend
Detailed view
  • app
    Folder
    • components
      Folder
      • portable-text-components
        Folder
        • portable-blocks
          Folder
          • Block{{.PascalCaseBlockTitle}}.tsx
            File
            View Code
            import * as React from "react"
            
            export default function Block{{.PascalCaseBlockTitle}}({ children }: { children: React.ReactNode }) {
              
              return (
                <div className="py-6 px-2 bg-primary">
                  <h1 className="text-primary-foreground">
                    {children}
                  </h1>
                </span>
              )
            }
            
          • index.ts
            File โ€ข Action File
            Actions
            1. Import Block component into portable-blocks index
              Behaviour: addMarkerAboveTarget
              Occurrence: first
              Target: const portableBlocks = {
              Content
              import Block{{.PascalCaseBlockTitle}} from "./Block{{.PascalCaseBlockTitle}}"
            2. Register block style in portable-blocks map
              Behaviour: addMarkerBelowTarget
              Occurrence: first
              Target: const portableBlocks = {
              Content
                {{.PortableBlockTitle}}: Block{{.PascalCaseBlockTitle}},
            View Code
            const portableBlocks = {
            }
            
            export default portableBlocks;
      • PortableText.tsx
        File โ€ข Action File
        Actions
        1. Import portable-blocks into PortableText
          Behaviour: addMarkerBelowTarget
          Occurrence: first
          Target: import {PortableText, type PortableTextComponents, type PortableTextBlock} from 'next-sanity'
          Content
          import portableBlocks from './portable-text-components/portable-blocks'
        2. Spread custom blocks into PortableText block map
          Behaviour: addMarkerBelowTarget
          Occurrence: first
          Target: block: {
          Content
                ...portableBlocks,
        View Code
        /**
         * This component uses Portable Text to render a post body.
         *
         * You can learn more about Portable Text on:
         * https://www.sanity.io/docs/block-content
         * https://github.com/portabletext/react-portabletext
         * https://portabletext.org/
         *
         */
        
        import {PortableText, type PortableTextComponents, type PortableTextBlock} from 'next-sanity'
        import portableTypes from './portable-text-components/portable-marks'
        
        import ResolvedLink from '@/app/components/ResolvedLink'
        
        export default function CustomPortableText({
          className,
          value,
        }: {
          className?: string
          value: PortableTextBlock[]
        }) {
          const components: PortableTextComponents = {
            types: {
              ...portableTypes,
            },
            block: {
              h1: ({children, value}) => (
                // Add an anchor to the h1
                <h1 className="group relative">
                  {children}
                  <a
                    href={`#${value?._key}`}
                    className="absolute left-0 top-0 bottom-0 -ml-6 flex items-center opacity-0 group-hover:opacity-100 transition-opacity"
                  >
                    <svg
                      xmlns="http://www.w3.org/2000/svg"
                      className="h-4 w-4"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke="currentColor"
                    >
                      <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth={2}
                        d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
                      />
                    </svg>
                  </a>
                </h1>
              ),
              h2: ({children, value}) => {
                // Add an anchor to the h2
                return (
                  <h2 className="group relative">
                    {children}
                    <a
                      href={`#${value?._key}`}
                      className="absolute left-0 top-0 bottom-0 -ml-6 flex items-center opacity-0 group-hover:opacity-100 transition-opacity"
                    >
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        className="h-4 w-4"
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke="currentColor"
                      >
                        <path
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          strokeWidth={2}
                          d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
                        />
                      </svg>
                    </a>
                  </h2>
                )
              },
            },
            marks: {
              link: ({children, value: link}) => {
                return <ResolvedLink link={link}>{children}</ResolvedLink>
              },
            },
          }
        
          return (
            <div className={['prose prose-a:text-brand', className].filter(Boolean).join(' ')}>
              <PortableText components={components} value={value} />
            </div>
          )
        }
        
Base Path
/studio/src
Detailed view
  • schemaTypes
    Folder
    • objects
      Folder
      • portable-text-components
        Folder
        • portable-text-blocks
          Folder
          • Block{{.PascalCaseBlockTitle}}Preview.tsx
            File
            View Code
            import React from 'react'
            
            export default function Block{{.PascalCaseBlockTitle}}Preview(props: any) {
              return (
                <span style={{ fontVariant: 'common-ligatures', display: 'inline' }}>
                  {props.children}
                </span>
              )
            }
            
      • blockContent.tsx
        File โ€ข Action File
        Actions
        1. Import Studio block style preview component
          Behaviour: addMarkerBelowTarget
          Occurrence: first
          Target: import {defineArrayMember, defineType, defineField} from 'sanity'
          Content
          import Block{{.PascalCaseBlockTitle}}Preview from './portable-text-components/portable-text-blocks/Block{{.PascalCaseBlockTitle}}Preview'
        2. Create default styles with custom block style
          Behaviour: replaceIfMissing
          Occurrence: first
          Target: type: 'block',
          Content
                styles: [
                  {title: 'Normal', value: 'normal'},
                  {title: 'H1', value: 'h1'},
                  {title: 'H2', value: 'h2'},
                  {title: 'H3', value: 'h3'},
                  {title: 'Quote', value: 'blockquote'},
                  {
                    title: '{{.BlockTitle}}',
                    value: '{{.PortableBlockStyle}}',
                    component: Block{{.PascalCasePortableBlockStyle}}Preview,
                  },
                ],
        3. Append custom block style to existing styles (if present)
          Behaviour: addMarkerBelowTarget
          Occurrence: first
          Target: styles: [
          Content
                  {
                    title: '{{.BlockTitle}}',
                    value: '{{.PortableBlockStyle}}',
                    component: Block{{.PascalCaseBlockTitle}}Preview,
                  },
        View Code
        import {defineArrayMember, defineType, defineField} from 'sanity'
        
        
        /**
         * This is the schema definition for the rich text fields used for
         * for this blog studio. When you import it in schemas.js it can be
         * reused in other parts of the studio with:
         *  {
         *    name: 'someName',
         *    title: 'Some title',
         *    type: 'blockContent'
         *  }
         *
         * Learn more: https://www.sanity.io/docs/block-content
         */
        export const blockContent = defineType({
          title: 'Block Content',
          name: 'blockContent',
          type: 'array',
          of: [
            defineArrayMember({
              type: 'block',
              marks: {
                annotations: [
                  {
                    name: 'link',
                    type: 'object',
                    title: 'Link',
                    fields: [
                      defineField({
                        name: 'linkType',
                        title: 'Link Type',
                        type: 'string',
                        initialValue: 'href',
                        options: {
                          list: [
                            {title: 'URL', value: 'href'},
                            {title: 'Page', value: 'page'},
                            {title: 'Post', value: 'post'},
                          ],
                          layout: 'radio',
                        },
                      }),
                      defineField({
                        name: 'href',
                        title: 'URL',
                        type: 'url',
                        hidden: ({parent}) => parent?.linkType !== 'href' && parent?.linkType != null,
                        validation: (Rule) =>
                          Rule.custom((value, context: any) => {
                            if (context.parent?.linkType === 'href' && !value) {
                              return 'URL is required when Link Type is URL'
                            }
                            return true
                          }),
                      }),
                      defineField({
                        name: 'page',
                        title: 'Page',
                        type: 'reference',
                        to: [{type: 'page'}],
                        hidden: ({parent}) => parent?.linkType !== 'page',
                        validation: (Rule) =>
                          Rule.custom((value, context: any) => {
                            if (context.parent?.linkType === 'page' && !value) {
                              return 'Page reference is required when Link Type is Page'
                            }
                            return true
                          }),
                      }),
                      defineField({
                        name: 'post',
                        title: 'Post',
                        type: 'reference',
                        to: [{type: 'post'}],
                        hidden: ({parent}) => parent?.linkType !== 'post',
                        validation: (Rule) =>
                          Rule.custom((value, context: any) => {
                            if (context.parent?.linkType === 'post' && !value) {
                              return 'Post reference is required when Link Type is Post'
                            }
                            return true
                          }),
                      }),
                      defineField({
                        name: 'openInNewTab',
                        title: 'Open in new tab',
                        type: 'boolean',
                        initialValue: false,
                      }),
                    ],
                  },
                ],
              },
            }),
          ],
        })
Raw JSON
Debug view of the fetched document
{
  "_createdAt": "2025-09-19T11:46:38Z",
  "_id": "56a03eb1-bfec-426e-8b2b-fbddb5b161ed",
  "_rev": "3JcS3A5GjOK7pB9VbWIKwJ",
  "_system": {
    "base": {
      "id": "56a03eb1-bfec-426e-8b2b-fbddb5b161ed",
      "rev": "K4tdAJtZqIlru9YbYKtw58"
    }
  },
  "_type": "command-slug",
  "_updatedAt": "2025-09-19T16:08:21Z",
  "description": "A line-based block you can have in your portable text content. (e.g., custom heading, lead paragraph, intro, callout)",
  "filePaths": [
    {
      "id": "path-1758111573363-2uw6i9l5w",
      "nodes": [
        {
          "_key": "1758114029952-6skyomi42",
          "_type": "treeNode",
          "actionFile": false,
          "actions": [],
          "children": [
            {
              "_key": "1758114044158-g3pao7lkk",
              "_type": "treeNode",
              "actionFile": false,
              "actions": [],
              "children": [
                {
                  "_key": "1758115726249-139rkyblr",
                  "_type": "treeNode",
                  "actionFile": false,
                  "actions": [],
                  "children": [
                    {
                      "_key": "1758115748903-gybtimxk6",
                      "_type": "treeNode",
                      "actionFile": false,
                      "actions": [],
                      "children": [
                        {
                          "_key": "1758116035646-s7soez0mj",
                          "_type": "treeNode",
                          "actionFile": false,
                          "actions": [
                            {
                              "_key": "3JcS3A5GjOK7pB9VbWIKl5",
                              "logic": {},
                              "mark": ""
                            }
                          ],
                          "children": [],
                          "code": "import * as React from \"react\"\n\nexport default function Block{{.PascalCaseBlockTitle}}({ children }: { children: React.ReactNode }) {\n  \n  return (\n    <div className=\"py-6 px-2 bg-primary\">\n      <h1 className=\"text-primary-foreground\">\n        {children}\n      </h1>\n    </span>\n  )\n}\n",
                          "id": "file-1758116035646",
                          "name": "Block{{.PascalCaseBlockTitle}}.tsx",
                          "nodeType": "file"
                        },
                        {
                          "_key": "1758115756801-jxu1o7xxr",
                          "_type": "treeNode",
                          "actionFile": true,
                          "actions": [
                            {
                              "_key": "3JcS3A5GjOK7pB9VbWIKm1",
                              "logic": {
                                "behaviour": "addMarkerAboveTarget",
                                "content": "import Block{{.PascalCaseBlockTitle}} from \"./Block{{.PascalCaseBlockTitle}}\"",
                                "occurrence": "first",
                                "requireAbsent": "Block{{.PascalCasePortableBlockTitle}}",
                                "target": "const portableBlocks = {"
                              },
                              "mark": "",
                              "title": "Import Block component into portable-blocks index"
                            },
                            {
                              "_key": "3JcS3A5GjOK7pB9VbWIKmx",
                              "logic": {
                                "behaviour": "addMarkerBelowTarget",
                                "content": "  {{.PortableBlockTitle}}: Block{{.PascalCaseBlockTitle}},",
                                "occurrence": "first",
                                "requireAbsent": "{{.PortableBlockTitle}}:",
                                "target": "const portableBlocks = {"
                              },
                              "mark": "",
                              "title": "Register block style in portable-blocks map"
                            }
                          ],
                          "children": [],
                          "code": "const portableBlocks = {\n}\n\nexport default portableBlocks;",
                          "id": "file-1758115756801",
                          "name": "index.ts",
                          "nodeType": "file"
                        }
                      ],
                      "code": "",
                      "id": "folder-1758115748903",
                      "name": "portable-blocks",
                      "nodeType": "folder"
                    }
                  ],
                  "code": "",
                  "id": "folder-1758115726249",
                  "name": "portable-text-components",
                  "nodeType": "folder"
                },
                {
                  "_key": "1758114053022-0lp5lu1ki",
                  "_type": "treeNode",
                  "actionFile": true,
                  "actions": [
                    {
                      "_key": "3JcS3A5GjOK7pB9VbWIKnt",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "import portableBlocks from './portable-text-components/portable-blocks'",
                        "occurrence": "first",
                        "requireAbsent": "import portableBlocks from './portable-text-components/portable-blocks'",
                        "target": "import {PortableText, type PortableTextComponents, type PortableTextBlock} from 'next-sanity'"
                      },
                      "mark": "",
                      "title": "Import portable-blocks into PortableText"
                    },
                    {
                      "_key": "3JcS3A5GjOK7pB9VbWIKop",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "      ...portableBlocks,",
                        "occurrence": "first",
                        "requireAbsent": "...portableBlocks",
                        "target": "block: {"
                      },
                      "mark": "",
                      "title": "Spread custom blocks into PortableText block map"
                    }
                  ],
                  "children": [],
                  "code": "/**\n * This component uses Portable Text to render a post body.\n *\n * You can learn more about Portable Text on:\n * https://www.sanity.io/docs/block-content\n * https://github.com/portabletext/react-portabletext\n * https://portabletext.org/\n *\n */\n\nimport {PortableText, type PortableTextComponents, type PortableTextBlock} from 'next-sanity'\nimport portableTypes from './portable-text-components/portable-marks'\n\nimport ResolvedLink from '@/app/components/ResolvedLink'\n\nexport default function CustomPortableText({\n  className,\n  value,\n}: {\n  className?: string\n  value: PortableTextBlock[]\n}) {\n  const components: PortableTextComponents = {\n    types: {\n      ...portableTypes,\n    },\n    block: {\n      h1: ({children, value}) => (\n        // Add an anchor to the h1\n        <h1 className=\"group relative\">\n          {children}\n          <a\n            href={`#${value?._key}`}\n            className=\"absolute left-0 top-0 bottom-0 -ml-6 flex items-center opacity-0 group-hover:opacity-100 transition-opacity\"\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"h-4 w-4\"\n              fill=\"none\"\n              viewBox=\"0 0 24 24\"\n              stroke=\"currentColor\"\n            >\n              <path\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                strokeWidth={2}\n                d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\"\n              />\n            </svg>\n          </a>\n        </h1>\n      ),\n      h2: ({children, value}) => {\n        // Add an anchor to the h2\n        return (\n          <h2 className=\"group relative\">\n            {children}\n            <a\n              href={`#${value?._key}`}\n              className=\"absolute left-0 top-0 bottom-0 -ml-6 flex items-center opacity-0 group-hover:opacity-100 transition-opacity\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                className=\"h-4 w-4\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke=\"currentColor\"\n              >\n                <path\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  strokeWidth={2}\n                  d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\"\n                />\n              </svg>\n            </a>\n          </h2>\n        )\n      },\n    },\n    marks: {\n      link: ({children, value: link}) => {\n        return <ResolvedLink link={link}>{children}</ResolvedLink>\n      },\n    },\n  }\n\n  return (\n    <div className={['prose prose-a:text-brand', className].filter(Boolean).join(' ')}>\n      <PortableText components={components} value={value} />\n    </div>\n  )\n}\n",
                  "id": "file-1758114053022",
                  "name": "PortableText.tsx",
                  "nodeType": "file"
                }
              ],
              "code": "",
              "id": "folder-1758114044158",
              "name": "components",
              "nodeType": "folder"
            }
          ],
          "code": "",
          "id": "folder-1758114029952",
          "name": "app",
          "nodeType": "folder"
        }
      ],
      "path": "frontend"
    },
    {
      "id": "path-1758109429918",
      "nodes": [
        {
          "_key": "1758110151454-1v3b9778v",
          "_type": "treeNode",
          "actionFile": false,
          "actions": [],
          "children": [
            {
              "_key": "1758109431965-lkxvfd11k",
              "_type": "treeNode",
              "actionFile": false,
              "actions": [],
              "children": [
                {
                  "_key": "1758109548620-mhlaenwvf",
                  "_type": "treeNode",
                  "actionFile": false,
                  "actions": [],
                  "children": [
                    {
                      "_key": "1758109732800-ovgdbke3h",
                      "_type": "treeNode",
                      "actionFile": false,
                      "actions": [],
                      "children": [
                        {
                          "_key": "1758110289262-qkvv7l69f",
                          "_type": "treeNode",
                          "actionFile": false,
                          "actions": [
                            {
                              "_key": "3JcS3A5GjOK7pB9VbWIKpl",
                              "logic": {},
                              "mark": ""
                            }
                          ],
                          "children": [],
                          "code": "import React from 'react'\n\nexport default function Block{{.PascalCaseBlockTitle}}Preview(props: any) {\n  return (\n    <span style={{ fontVariant: 'common-ligatures', display: 'inline' }}>\n      {props.children}\n    </span>\n  )\n}\n",
                          "id": "file-1758110289262",
                          "name": "Block{{.PascalCaseBlockTitle}}Preview.tsx",
                          "nodeType": "file"
                        }
                      ],
                      "code": "",
                      "id": "folder-1758109732800",
                      "name": "portable-text-blocks",
                      "nodeType": "folder"
                    }
                  ],
                  "code": "",
                  "id": "folder-1758109548620",
                  "name": "portable-text-components",
                  "nodeType": "folder"
                },
                {
                  "_key": "1758109462849-a1dhia33f",
                  "_type": "treeNode",
                  "actionFile": true,
                  "actions": [
                    {
                      "_key": "3JcS3A5GjOK7pB9VbWIKqh",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "import Block{{.PascalCaseBlockTitle}}Preview from './portable-text-components/portable-text-blocks/Block{{.PascalCaseBlockTitle}}Preview'",
                        "occurrence": "first",
                        "requireAbsent": "Block{{.PascalCasePortableBlockStyle}}Preview",
                        "target": "import {defineArrayMember, defineType, defineField} from 'sanity'"
                      },
                      "mark": "",
                      "title": "Import Studio block style preview component"
                    },
                    {
                      "_key": "3JcS3A5GjOK7pB9VbWIKrd",
                      "logic": {
                        "behaviour": "replaceIfMissing",
                        "content": "      styles: [\n        {title: 'Normal', value: 'normal'},\n        {title: 'H1', value: 'h1'},\n        {title: 'H2', value: 'h2'},\n        {title: 'H3', value: 'h3'},\n        {title: 'Quote', value: 'blockquote'},\n        {\n          title: '{{.BlockTitle}}',\n          value: '{{.PortableBlockStyle}}',\n          component: Block{{.PascalCasePortableBlockStyle}}Preview,\n        },\n      ],",
                        "occurrence": "first",
                        "replacement": "    type: 'block',\n      styles: [\n        {title: 'Normal', value: 'normal'},\n        {title: 'H1', value: 'h1'},\n        {title: 'H2', value: 'h2'},\n        {title: 'H3', value: 'h3'},\n        {title: 'Quote', value: 'blockquote'},\n        {\n          title: '{{.BlockTitle}}',\n          value: '{{.PortableBlockTitle}}',\n          component: Block{{.PascalCaseBlockTitle}}Preview,\n        },\n      ],",
                        "requireAbsent": "styles: [",
                        "target": "type: 'block',",
                        "targetStart": "type: 'block',"
                      },
                      "mark": "",
                      "title": "Create default styles with custom block style"
                    },
                    {
                      "_key": "3JcS3A5GjOK7pB9VbWIKsZ",
                      "logic": {
                        "behaviour": "addMarkerBelowTarget",
                        "content": "        {\n          title: '{{.BlockTitle}}',\n          value: '{{.PortableBlockStyle}}',\n          component: Block{{.PascalCaseBlockTitle}}Preview,\n        },",
                        "occurrence": "first",
                        "requireAbsent": "value: '{{.PortableBlockStyle}}'",
                        "target": "styles: ["
                      },
                      "mark": "",
                      "title": "Append custom block style to existing styles (if present)"
                    }
                  ],
                  "children": [],
                  "code": "import {defineArrayMember, defineType, defineField} from 'sanity'\n\n\n/**\n * This is the schema definition for the rich text fields used for\n * for this blog studio. When you import it in schemas.js it can be\n * reused in other parts of the studio with:\n *  {\n *    name: 'someName',\n *    title: 'Some title',\n *    type: 'blockContent'\n *  }\n *\n * Learn more: https://www.sanity.io/docs/block-content\n */\nexport const blockContent = defineType({\n  title: 'Block Content',\n  name: 'blockContent',\n  type: 'array',\n  of: [\n    defineArrayMember({\n      type: 'block',\n      marks: {\n        annotations: [\n          {\n            name: 'link',\n            type: 'object',\n            title: 'Link',\n            fields: [\n              defineField({\n                name: 'linkType',\n                title: 'Link Type',\n                type: 'string',\n                initialValue: 'href',\n                options: {\n                  list: [\n                    {title: 'URL', value: 'href'},\n                    {title: 'Page', value: 'page'},\n                    {title: 'Post', value: 'post'},\n                  ],\n                  layout: 'radio',\n                },\n              }),\n              defineField({\n                name: 'href',\n                title: 'URL',\n                type: 'url',\n                hidden: ({parent}) => parent?.linkType !== 'href' && parent?.linkType != null,\n                validation: (Rule) =>\n                  Rule.custom((value, context: any) => {\n                    if (context.parent?.linkType === 'href' && !value) {\n                      return 'URL is required when Link Type is URL'\n                    }\n                    return true\n                  }),\n              }),\n              defineField({\n                name: 'page',\n                title: 'Page',\n                type: 'reference',\n                to: [{type: 'page'}],\n                hidden: ({parent}) => parent?.linkType !== 'page',\n                validation: (Rule) =>\n                  Rule.custom((value, context: any) => {\n                    if (context.parent?.linkType === 'page' && !value) {\n                      return 'Page reference is required when Link Type is Page'\n                    }\n                    return true\n                  }),\n              }),\n              defineField({\n                name: 'post',\n                title: 'Post',\n                type: 'reference',\n                to: [{type: 'post'}],\n                hidden: ({parent}) => parent?.linkType !== 'post',\n                validation: (Rule) =>\n                  Rule.custom((value, context: any) => {\n                    if (context.parent?.linkType === 'post' && !value) {\n                      return 'Post reference is required when Link Type is Post'\n                    }\n                    return true\n                  }),\n              }),\n              defineField({\n                name: 'openInNewTab',\n                title: 'Open in new tab',\n                type: 'boolean',\n                initialValue: false,\n              }),\n            ],\n          },\n        ],\n      },\n    }),\n  ],\n})",
                  "id": "file-1758109462849",
                  "name": "blockContent.tsx",
                  "nodeType": "file"
                }
              ],
              "code": "",
              "id": "folder-1758109431965",
              "name": "objects",
              "nodeType": "folder"
            }
          ],
          "code": "",
          "id": "folder-1758110151454",
          "name": "schemaTypes",
          "nodeType": "folder"
        }
      ],
      "path": "/studio/src"
    }
  ],
  "goals": [
    {
      "_key": "b2f311f75c6e56651a0beea3a678ea38",
      "description": "Adds a reusable block style to the Studio editor (with optional preview component) and renders it on the frontend by extending the PortableText block map.",
      "fileHints": [],
      "howToTips": [],
      "title": "Wire a custom Portable Text block style across Studio & Frontend"
    }
  ],
  "ignoredPatterns": [],
  "slug": {
    "_type": "slug",
    "current": "add-portable-text-block"
  },
  "slugCurrent": "add-portable-text-block",
  "title": "Add Portable Text Block",
  "variables": [
    {
      "_type": "variableDefinition",
      "description": "Human-friendly label shown in the Studio style dropdown.\n",
      "examples": [
        "Custom Heading",
        "Lead Paragraph",
        "Intro",
        "Callout"
      ],
      "name": "BlockTitle",
      "priority": 1,
      "title": "BlockTitle"
    }
  ]
}