Querying a single field from a Sanity schema using groq in Node.js

50 replies
Last updated: Sep 1, 2022
Hello everyone! I am not sure why I am having troubles with this, but I have a schema from which I am trying to only query a single field from, it's an image. It looks like this:
const filter = groq`*[_type == "siteSettings"]`;
  const projection = groq`{ image: gigs_image.asset->url}`;
  const query = [filter, projection].join(' ');
  const image = await client.fetch(query).catch(error => console.error(error));
I adapted this query from a different place, where all of the fields of
siteSettings
is used. But here, I only need that one field
gigs_image
. I know I can get that specific field with groq`*[_type == "siteSettings"]{gigs_image}`, but I am not sure how to get the..
.asset->url
stuff there.. I am sorry this is a really poorly phrased question, I am still super new to sanity.
AI Update

No worries, this is a common question when you're getting started with GROQ! The good news is your query is actually really close – you just need to simplify it a bit.

You can absolutely include the .asset->url reference directly in your projection. Here's how to write it:

const query = groq`*[_type == "siteSettings"]{ "image": gigs_image.asset->url }`;
const result = await client.fetch(query).catch(error => console.error(error));

Or if you want the first (and likely only) siteSettings document:

const query = groq`*[_type == "siteSettings"][0]{ "image": gigs_image.asset->url }`;
const result = await client.fetch(query).catch(error => console.error(error));

What's happening here:

The -> operator in GROQ is called the dereference operator, and it follows references to other documents. When you have an image field in Sanity, it stores a reference to an asset document. Using gigs_image.asset->url tells GROQ to:

  1. Get the gigs_image field
  2. Follow the reference in asset to the actual image asset document
  3. Return the url field from that asset

A few tips:

  • You don't need to split your filter and projection into separate variables – GROQ queries work as a single pipeline as explained in the projections documentation
  • The quotes around "image" create an alias for the field in your result (so you get { image: "https://..." } instead of { url: "https://..." })
  • If you want the full image object with metadata, you could use gigs_image.asset-> without the .url at the end
  • Since *[_type == "siteSettings"] returns an array, adding [0] gets you the first document directly, which is cleaner if you only have one settings document

Hope this helps! GROQ's reference following with -> is super powerful once you get the hang of it.

I think I get what you're going for! Why are you defining the query and the projection separately and then joining them?
The following should return just the url you're looking for:

`*[_type == "siteSettings"].gigs_image.asset->.url
hm, this gives me
null
and, it returns an object
What version of the API are you using?
I am not sure I can answer that question
my package json says
"@sanity/client": "^2.0.0",
How have you configured your client?
"groq": "^0.142.0",
does that help?
the query is done by node, not by clientside javascript
if that makes a difference
Where and how are you running the query?
it's running as the site is being built by eleventy, via node
Ok, how have you added the client to your site?
there is no clientside javascript running
this is happening during build
I understand that. How have you connected your site to sanity?
... I don't know what that question means
I added a webhook to netlify, whenever the content changes in production, netlify triggers a rebuild
Where are you using this package?
I have a file that requires it and configures it with env vars
OK, what does that look like? That would be your client configuration.
but I never touched that, it was part of the eleventy starter
require('dotenv').config({
  path: `.env.${process.env.NODE_ENV || 'development'}`
})
const sanityClient = require("@sanity/client");

const { sanity } = require('../../client-config')

/**
 * Set manually. Find configuration in
 * studio/sanity.json or on <http://manage.sanity.io|manage.sanity.io>
 */

/*
const sanity = {
  projectId: 'anokeucs',
  dataset: 'eleventy',
  useCdn: true
}
*/

module.exports = sanityClient({...sanity, useCdn: !process.env.SANITY_READ_TOKEN, token: process.env.SANITY_READ_TOKEN});
so just to make sure I expressed myself correctly, I am getting everything as I wanted, I am just trying to query a single field instead of the entire schema with all of the fields. Instead of doing
settings.gigs_image
I just wanna use
gigs_image
I understand that the query for the field is
*[_type == "siteSettings"]{gigs_image}
according to doc. But I don't know how to use
.asset->url
in this context, because when I just add it to the query on top, it fails
I'm sorry that I am lacking the vocabulary to express myself correctly, I should have not relied on a starter. It's basically a black box for me with abstraction that is entirely foreign 😕
No worries, I understand! That first query I sent you would get you the url of the image you're looking for if you're using the right API version. It looks like your client is missing an API version , so it falls back to v1 which doesn't have support for that sort of query. If you add
apiVersion: '2022-08-29'
to the
sanity
object that contains your project id and dataset it should fix this.
If you want your data in it in the form of an object, your first message looks correct. That may have been failing because of the missing API version.
alright, I have added the
apiVersion
to the sanity object for the clientConfig. But with groq`*[_type == "siteSettings"].gigs_image.asset-&gt;.url`, it still returns
null
What does the schema for that page look like?
export default {
  name: 'siteSettings',
  type: 'document',
  title: 'Einstellungen',
  __experimental_actions: ['update', /* 'create', 'delete', */ 'publish'],
  fields: [
    {
      name: 'email',
      type: 'string',
      title: 'E-Mail*',
      validation: Rule => Rule.required()
    }, {
      name: 'image',
      type: 'image',
      title: 'SEO Bild*',
      description: 'Dieses Bild wird von der Suchmaschine verwendet.',
      validation: Rule => Rule.required()
    }, {
      name: 'sharing_image',
      type: 'image',
      title: 'Social Media Bild*',
      description: 'Dieses Bild wird angezeigt, wenn jemand den Link der Website auf Social Media geteilt wird.',
      validation: Rule => Rule.required()
    }, {
      name: 'gigs_image',
      type: 'image',
      title: 'Hero-Image: Gigs',
      description: 'Test test',
      validation: Rule => Rule.required()
    }
  ]
}
Have you added an image and published the document?
yes
I can access the asset url, if I do it the other way
In this query:
const filter = groq`*[_type == "siteSettings"]`;
  const projection = groq`{email, "image": image.asset->url, "sharing_image": sharing_image.asset->url, "gigs_image": gigs_image.asset->url}`;
  const query = [filter, projection].join(' ');
  const sanitySettings = await client.fetch(query).catch(error => console.error(error));
  const staticSettings = {
    domain: process.env.URL || '<http://localhost:8080>',
    thisYear: format(new Date(), 'yyyy'),
  };
  const settings = {...sanitySettings[0], ...staticSettings};
but I don't want all of this other stuff from the data, I only want that one, single
gigs_image
*[_type == "siteSettings"]
will return an array. Can you please try RD’s suggestion but slicing the first document?

*[_type == "siteSettings"][0].gigs_image.asset->.url
(Edit: Though I’d still be surprised you get a
null
response. I’d at least expect an array.)
it still returns `null`:/
Would you mind showing the return from
*[_type == "siteSettings"]
in Vision? For you to get data from
*[_type == "siteSettings"]{gigs_image}
but null from ``*[_type == "siteSettings"].gigs_image` means we must be misunderstanding something in your configuration.
this is what it returns:
[
  {
    _createdAt: '2019-03-29T10:09:19Z',
    _id: 'siteSettings',
    _rev: 'ns3tjWS9mvqkIfdQ8S1ltp',
    _type: 'siteSettings',
    _updatedAt: '2022-08-29T19:35:34Z',
    email: '',
    gigs_image: { _type: 'image', asset: [Object] },
    image: { _type: 'image', asset: [Object] },
    sharing_image: { _type: 'image', asset: [Object] }
  }
]
wait.. I am not sure what "Vision" is
Vision is a plugin in the Studio that lets you run GROQ queries quickly, but the above is equivalent to what you would have gotten there. 👍
Could you please post your snippet of code showing how you’re implementing the naked projection (i.e.,
*[_type == "siteSettings"][0].gigs_image
or something similar rather than using a projection) using the
filter
,
query
,
settings
, etc. in your previous posts?
I am not sure I fully understand what you mean. Here's my entire code, that I am using right now.
const query = groq`*[_type == "siteSettings"]{"url": gigs_image.asset->url}`;
  const image = await client.fetch(query).catch(error => console.error(error));
  
  return image[0].url;
This gives me what I want, but I don't understand why I can't just query do it without the
"url"
, it seems unnecessary
but maybe this is just how groq works? I really don't know
sorry I can't get syntax highlighting to work in slack? Idk what's going on
What does this give you?

const query = groq`*[_type == "siteSettings"][0].gigs_image.asset->url`;
  const image = await client.fetch(query).catch(error => console.error(error));
  
  return image;
Sorry for the late reply! It gives me back this:
[
  {
    url: '<https://cdn.sanity.io/images/gpygc6vl/production/a7f555027d8a791d9b4dd2915bf4ecbcb3019d36-1600x1600.webp>'
  }
]
Interesting. Is your client configured on
v1
(or with no
apiVersion
in the config) by chance?
user M
considered this a while ago and advised me to add `apiVersion: '2022-08-29'`to the client-config, which I did!

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?