GraphQL not returning bullet and link types from Portable Text block

30 replies
Last updated: Jan 4, 2023
All portableText Elements come out in graphql as either on of the following types:
span
,
h2
,
h3
But not as
bullet
and
link
, although they are correctly formatted within Studio.
export default {
  name: 'pageTextSection',
  title: 'Page Text Section',
  type: 'document',
  fields: [
    {
      name: 'body',
      title: 'Body',
      type: 'array',
      of: [{type: 'block'}],
    },
  ],
}
Am I missing something here?
AI Update

You're not missing anything in your schema - this is expected behavior with how Portable Text structures data!

Bullets and links aren't separate block types - they're represented differently in the data structure:

Bullets (List Items)

Bullets are still blocks with _type: 'block', but they have additional properties:

  • listItem: 'bullet' (or 'number' for numbered lists)
  • level: 1 (for nesting level)

In GraphQL, they come through as blocks, not as a separate "bullet" type. You need to check the listItem and level fields on the block to identify them.

Links are stored as annotations in the markDefs array, not as separate block types. The actual link text is still a span with a mark property that references the annotation. The structure looks like:

{
  "_type": "block",
  "children": [
    {
      "_type": "span",
      "text": "click here",
      "marks": ["abc123"]  // references the markDef
    }
  ],
  "markDefs": [
    {
      "_key": "abc123",
      "_type": "link",
      "href": "https://example.com"
    }
  ]
}

How to Handle This in GraphQL

When querying GraphQL, you'll need to:

  1. Look for blocks with listItem and level properties to identify list items
  2. Check the markDefs array on each block for link annotations
  3. Match the marks array on spans to the _key in markDefs to connect linked text to URLs

This is all correctly formatted in Studio because the Portable Text editor understands this structure. Your GraphQL client needs to handle the same relationships when rendering.

I'd recommend using a Portable Text renderer library for your frontend framework to handle this complexity automatically - it will properly parse listItem, markDefs, and all the other Portable Text conventions for you. Check out the guide on presenting block text for more details on rendering Portable Text.

Show original thread
30 replies
I expect index 5,6, and 7 to be bullets.
Can you expand one of those objects and share it's json?
Sure, I created a shorter example:
[
    {
        "_key": "aed161c0569e",
        "_type": "block",
        "style": "normal",
        "list": null,
        "_rawChildren": [
            {
                "_key": "2516df903ff6",
                "_type": "span",
                "marks": [],
                "text": "Lorem Ipsum"
            }
        ],
        "children": [
            {
                "text": "Lorem Ipsum",
                "marks": [],
                "_type": "span",
                "_key": "2516df903ff6"
            }
        ]
    },
    {
        "_key": "22af06ab3b90",
        "_type": "block",
        "style": "normal",
        "list": null,
        "_rawChildren": [
            {
                "_key": "f51fbcf35985",
                "_type": "span",
                "marks": [],
                "text": "Test"
            }
        ],
        "children": [
            {
                "text": "Test",
                "marks": [],
                "_type": "span",
                "_key": "f51fbcf35985"
            }
        ]
    },
    {
        "_key": "c7c5681a9186",
        "_type": "block",
        "style": "normal",
        "list": null,
        "_rawChildren": [
            {
                "_key": "f88248e26fa4",
                "_type": "span",
                "marks": [],
                "text": "Test 2"
            }
        ],
        "children": [
            {
                "text": "Test 2",
                "marks": [],
                "_type": "span",
                "_key": "f88248e26fa4"
            }
        ]
    },
    {
        "_key": "9fe61d539e91",
        "_type": "block",
        "style": "normal",
        "list": null,
        "_rawChildren": [
            {
                "_key": "050e64a909db",
                "_type": "span",
                "marks": [],
                "text": "Test 3"
            }
        ],
        "children": [
            {
                "text": "Test 3",
                "marks": [],
                "_type": "span",
                "_key": "050e64a909db"
            }
        ]
    }
]
And this is my GastbyJS GraphQL part:


...
				... on SanityPageTextSection {
					_id
					_type
					body {
						_key
						_type
						style
						list
						_rawChildren
						children {
							text
							marks
							_type
							_key
						}
					}
				}
...
My expectation was to see the last 3 lines as a list in the frontend
user J
Please help here, as
user M
seems to be offline.
Please don't tag members that aren't already involved in a thread. There are over 35,000 members of this community, all looking for help. Team members also need to do things like sleep and eat so we can't be present 24 hours a day. It's important to keep this in mind and be patient and respectful while waiting for an answer.
What does the data for these list items look like if you inspect them in the Studio?
user M
In VISION they seem to be ok., see Screenshot. P.S.: just added Saskia to the thread, since she works from the time zone like I do and I also need to sleep. So I didn't want to rush anybody, I just considered it to be more efficient, to talk to an Support Engineer in my time zone.
I understand that line of thinking. Unfortunately, that's not quite how the system works and can end up just confusing the issue and derailing a team member's work flow. You can get a dedicated support engineer in your timezone on an Enterprise plan, but that's usually overkill unless you have a large project or many users.
To pin this down further: is the result you're sharing coming from a query in your frontend that's relying on gatsby-source-sanity or in your GraphQL playground? If it's the former, do you get the same behavior in your playground?
user M
Thank you for your answer. The last screenshot is directly from SANITY STUDIO's VISION:

*[_type == "pageTextSection"]

The wrong screenshot from earlier today is taken from gatsby's graphiql interface using the gatsby sanity plugin you mentioned:


{
	resolve: `gatsby-source-sanity`,
		options: {
			...clientConfig.sanity,
			token,
			watchMode: !isProd,
			overlayDrafts: !isProd && token,
			},
		}, 
Got it! Did you happen to deploy a public playground that we can take a look at? It would have been an option you selected when you deployed your GraphQL API.
Hmm, public in what sense? Are you talking about that gatsby stuff? I can shar the git repo with you
No, I mean specifically when you ran either
sanity graphql deploy
or
npm deploy-graphql
(depending on which version of the Studio you're in). It would have prompted you during that flow to create a playground with a shareable link others can use.
I'm in v3 and I deployed all my sanity stuff to https://hppycdng.sanity.studio/ with
sanity deploy
and
sanity graphql deploy
And just be complete, on the gatsby side, I'm using:
"@sanity/client": "^4.0.0", "gatsby-source-sanity": "^7.5.1",
Can you redeploy your GraphQL API, create a playground by selecting Y when it asks
Do you want to enable a GraphQL playground?
and then share that link here so that we can explore your data? This will help us figure out if it's an issue with gatsby-source-sanity or our CLI tooling.
ok, give me a sec
Thanks! Diving into this now
Ok! It looks like the listItem field is being properly applied in your GraphQL Playground:
{
            "_key": "d1c3b1830c0a",
            "_type": "block",
            "children": [
              {
                "_key": "78ee636c1919",
                "_type": "span",
                "marks": [],
                "text": "Test"
              }
            ],
            "level": 1,
            "listItem": "number",
            "markDefs": [],
            "style": "normal"
          },
So it seems to be an issue with the gatsby-source-sanity plugin. Can you share your Gatsby build's package.json so I can create a bug ticket?
yes, give me a sec
{
  "name": "hppycdng",
  "version": "1.0.0",
  "private": true,
  "description": "hppycdng",
  "author": "Matthias Radscheit",
  "keywords": [
    "gatsby"
  ],
  "scripts": {
    "develop": "gatsby develop",
    "start": "gatsby develop",
    "build": "gatsby build",
    "serve": "gatsby serve",
    "clean": "gatsby clean",
    "serve-preview": "NODE_ENV=development gatsby develop --host 0.0.0.0 --p $PORT"
  },
  "dependencies": {
    "@chakra-ui/gatsby-plugin": "^3.0.6",
    "@chakra-ui/pro-theme": "^0.0.57",
    "@chakra-ui/react": "^2.3.7",
    "@emotion/react": "^11.10.5",
    "@emotion/styled": "^11.10.5",
    "@fontsource/inter": "^4.5.14",
    "@portabletext/react": "^2.0.0",
    "@sanity/asset-utils": "^1.3.0",
    "@sanity/client": "^4.0.0",
    "@sanity/image-url": "^1.0.1",
    "date-fns": "^2.29.3",
    "dotenv": "^16.0.3",
    "framer-motion": "^7.6.4",
    "gatsby": "^5.3.3",
    "gatsby-plugin-image": "^3.3.2",
    "gatsby-plugin-manifest": "^5.3.1",
    "gatsby-plugin-sanity-image": "^0.13.3",
    "gatsby-plugin-sharp": "^5.3.2",
    "gatsby-plugin-sitemap": "^6.3.1",
    "gatsby-source-filesystem": "^5.3.1",
    "gatsby-source-sanity": "^7.5.1",
    "gatsby-transformer-sharp": "^5.3.1",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "react-icons": "^4.6.0"
  }
}
If possible, please add me to the bug ticket:
@radscheit
Thanks! One more thing to check before I file this. It looks like your query is using the
body
field. Do you have a
_rawBody
field available? If so, you may get the intended behavior if you change it to:
... on SanityPageTextSection {
					_id
					_type
					_rawBody {
						_key
						_type
						style
						list
						_rawChildren
						children {
							text
							marks
							_type
							_key
						}
				
ok, I'll check this and come back to you in a minute
I think you actually have to leave the projection off, though. It may require this:
on SanityPageTextSection {
					_id
					_type
					_rawBody 
}
user M
Oh great, you were damn right. Thank you very much. Can you please expand on the semantical difference between the values und their _raw*s or share a link to an explanation?
Oh man, I'm so glad I thought of that! I'm pretty unpracticed when it comes to GraphQL, so I'm sorry it didn't occur to me sooner. The plugin's docs speak to it here . That's about all I know about why it works that way.
Great, thank you for your continious support 🙂 Have a nice day.
You too!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?