Querying multiple types in Sanity with vanilla JS and using webhooks for Netlify rebuilds

46 replies
Last updated: Jan 20, 2022
How do I query multiple _types? I've tried using the OR operator, and making an array in the filter area, but neither seem to work. I can't find documentation for this specific issue either. Thanks!
AI Update

You can query multiple _type values using the in operator in GROQ! The syntax looks like this:

*[_type in ["article", "category"]]

This will return all documents where the _type is either "article" OR "category". The in operator checks if a value exists in an array, which is exactly what you need for filtering by multiple document types.

Here's a more complete example with a projection:

*[_type in ["post", "page", "product"]]{
  _id,
  _type,
  title,
  slug
}

This query fetches all documents that are either posts, pages, or products, and returns their ID, type, title, and slug fields.

If you tried using an array directly in the filter like *[_type == ["article", "category"]], that won't work because you're comparing _type to an array rather than checking if it exists within an array. The in operator is the correct way to handle this.

You can also combine this with other filter conditions using the && (AND) or || (OR) operators:

*[_type in ["article", "blogPost"] && publishedAt > "2024-01-01"]

The key thing to remember is: use in when you want to check if a field's value matches any value in an array. This is documented in the GROQ pipeline components section, though it can be easy to miss!

Hey 👋
*[_type in ["typeA", "typeB"]]
should do it I think. The result might be confusing though if you have fields with the same name in both types.
would it be better to do multiple queries and then join the arrays? I want to be able to have mixed-type results fed into a script loop in one payload
maybe this helps if I understand you correctly ?
*[_type in ["typeA", "typeB"]]{
  _type == "typeA" => {
    // field selection for typeA
  },
  _type == "typeB" => {
    // field selection for typeB
  }
}

Yes, I'll try that - thanks!
user Q
The filter part is working great, thanks! However, I'm getting an error with the projection part of it:
"expected '}' following object body"

Here is what I have:

let encodedFilter = encodeURIComponent('[_type in ["cabinet", "representative", "supremeCourt", "senator"]]');
                    let encodedProjection = encodeURIComponent(`_type== "cabinet" => {"_type"}, _type== "representative" => {"_type"}, _type== "supremeCourt" => {"_type"}, _type== "senator" => {"_type"}`)
                        fetch(`<https://6dyl9k59.api.sanity.io/v2021-01-01/data/query/production?query=*${encodedFilter}{${encodedProjection}}>`)
I'm only grabbing the _type for proof of syntax. I'll fill it in with the varied projections afterwards
user M
any ideas on how to have multiple projections?
Ah, I think I see the issue! It looks like you're technically on
v1
of the API since you have
2021-01-01
specified. If you configure it to use a date later than
2021-03-25
do you still get the error?
wow, I meant to put
2022-01-01
! I've updated it to that, and I still get the error
What frontend framework are you using?
none, vanilla JS.
Do you want to see my git repo? It's a very simple SPA
Sure! That would be helpful.
Which fields are you trying to get from your different types in your projections?
I have 4 kinds of documents &amp; a 5th option of combining them in one fetch, they each have proper projections in the file (I updated it since I pasted code into the thread)
What I mean is, what are the fields within those documents you're hoping to show? To clarify, your query should look something like this before being encoded:
const query = `[_type in ["cabinet", "representative", "supremeCourt", "senator"]]{
  _type == 'representative' => {
    //fields you're hoping to get from representative document
  }
 //other types
}`
What you currently have would be this:

const query = `[_type in ["cabinet", "representative", "supremeCourt", "senator"]]{
  _type == 'representative' => {
    "_type"
  }
  //etc
}`

let encodedProjection = encodeURIComponent(`
                        _type == "cabinet" => {"_type, cabinetName, 'confirmationCommittee': confirmationCommittee[]->.senateCommitteeName, position, succession, cardLink, cardLink2, cardBackImage"}, 
                        _type == "representative" => {"_type, representativeName, state->, party->, district, assumedOffice, 'leadershipPositions': leadershipPositions[]->.houseLeadershipName, 'committeeAssignments': committeeAssignments[]->.CommitteeName, cardLink, cardBackImage"}, 
                        _type == "supremeCourt" => {"_type, circuitAssignments[], justiceName, memberSince, nominatedBy, position, segalCoverScore, cardLink, cardBackImage"}, 
                        _type == "senator" => {"_type, senatorName, party->, state->, leadershipPositions[], 'committeeAssignments': committeeAssignments[]->.senateCommitteeName, reelectionYear, cardLink, assumedOffice, cardBackImage"}`
                    )
this should be what is in the file...I'm looking at github on my browser and that is what I copied this from
it is the same projections as the individual ones above the "all" fetch
Got it. I was basing that off of what you'd shared in this thread. Can you try changing
=>
to
=>
?
sure! one sec
sure! one sec
This is now what I get. BTW thank you for this help!
{description: "Attribute or a string key expected", type: "queryParseError"}
Happy to help! Let me play around with this a bit more....
absolutely, I'm not planning on continuing work on this until tomorrow.
OK! There were some extra apostrophes in your projection. I can get the query to work if you change it to the following:
let encodedFilter = encodeURIComponent('[_type in ["cabinet", "representative", "supremeCourt", "senator"]]');
let encodedProjection = encodeURIComponent(`
                        _type == "cabinet" => {_type, cabinetName, 'confirmationCommittee': confirmationCommittee[]->.senateCommitteeName, position, succession, cardLink, cardLink2, cardBackImage}, 
                        _type == "representative" => {_type, representativeName, state->, party->, district, assumedOffice, 'leadershipPositions': leadershipPositions[]->.houseLeadershipName, 'committeeAssignments': committeeAssignments[]->.CommitteeName, cardLink, cardBackImage}, 
                        _type == "supremeCourt" => {_type, circuitAssignments[], justiceName, memberSince, nominatedBy, position, segalCoverScore, cardLink, cardBackImage}, 
                        _type == "senator" => {_type, senatorName, party->, state->, leadershipPositions[], 'committeeAssignments': committeeAssignments[]->.senateCommitteeName, reelectionYear, cardLink, assumedOffice, cardBackImage}
                    `)
                        fetch(`<https://6dyl9k59.api.sanity.io/v2022-01-01/data/query/production?query=*${encodedFilter}{${encodedProjection}}>`)
                        .then(res => res.json())
                        .then(justice => justice.result.forEach((item)=>{
                                cardGrid.append(createCard(item));
                            }),
                            main.append(cardGrid))
One thing that may be helpful to look into is installing the JS Client on your project. This will allow you to pass a config to your Sanity Client and avoid having to use
encodeURI
. I'll try to work up an example for you.
One thing that may be helpful to look into is installing the JS Client on your project. This will allow you to pass a config to your Sanity Client and avoid having to use
encodeURI
. I'll try to work up an example for you.
wonderful, thank you! I was trying to use the minimum number of tools/npm packages on this site, really keep it as vanilla as possible...but maybe I went a step too far?
wonderful, thank you! I was trying to use the minimum number of tools/npm packages on this site, really keep it as vanilla as possible...but maybe I went a step too far?
user M
you mentioned using the JS client - which (as far as I'm aware) is a node.js package. How does this work if I'm hosting on Netlify which only (again, as far as I understand) has node.js operations during its build cycle? Sorry if this is an obvious question, but I've read a TON of your documentation and haven't found the answer.
Nope, you're correct! I realize now that you won't be able to use a node package in a vanilla JS app. Apologies!
So, my understanding is that Sanity is very jamstack oriented. I'm moving from traditional, hard-coded HTML/CSS, and just learned the basics of JS. Therefore, I'm trying to minimize dependencies &amp; frameworks, but I want to avoid anything where my clients will need to rebuild a netlify deployment after adding/altering Sanity data. How would one do that if using the JS client? If you can't, where is this described/discussed?
This would have more to do with Netlify than Sanity. If your site is statically generated, you'll need to rebuild it each time you publish new content. Content is available in your Content Lake as soon as it's added. You'll need to set up something like webhooks to automatically trigger a site rebuild when content is published if you're trying to keep your clients from having to do it.
Ahhh, I believe I need to look into webhooks! I didn't know that was the missing piece. Thanks again.

A soft suggestion: have an article/video/doc showing how to do a vanilla JS, frameworkless project that auto-triggers a rebuild upon some sort of "rebuild" button or something. It seems like that's a core use-case that isn't covered in your current docs, no?
That's a good suggestion! I actually intended to write a guide to using Sanity with vanilla JS/CSS/HTML waaaay back when I first started using it since I didn't know any frameworks at the time.
Yaay!! I just finished a draft of some slick logic that uses the query params to generate a fetch command. If you'd like, I can send it to you once I have it tightened up a bit...
It allows for unlimited number of types, filters, and sorts without coding any specifics other than the projections, which are done in one big array. I'm not done, but if I understand this all correctly, it would be a really powerful piece of logic
That way sort and filter commands just change the query params, and you can always share the specific set of filter/sort settings
It seems a shame to have to use a framework with Sanity, when one of its upsides is how lightweight it is. Amazing for small projects!
Yeah, I'd love to see it! If you'd like, you can also publish it as a snippet or guide on the Exchange . That way you the get credit for you work!
Sure, I'd just want to have confirmation from you or someone else that I didn't make a rookie mistake. I just learned JS a few months ago!
actually, here is what I have so far:
            //Set up query vars
            const projectId = '6dyl9k59';
            const apiDate = 'v2022-01-01';
            const dataSet = 'production';
            const encodedProjection = encodeURIComponent(`_type == 'secretaries' => {_type, name, 'confirmationCommittee': confirmationCommittee[]->.senateCommitteeName, position, succession, cardLink, cardLink2, cardBackImage}, _type == 'representatives' => {_type, name, state->, party->, district, assumedOffice, 'leadershipPositions': leadershipPositions[]->.houseLeadershipName, 'committeeAssignments': committeeAssignments[]->.CommitteeName, cardLink, cardBackImage}, _type == 'justices' => {_type, circuitAssignments[], name, memberSince, nominatedBy, position, segalCoverScore, cardLink, cardBackImage}, _type == 'senators' => {_type, name, party->, state->, leadershipPositions[], 'committeeAssignments': committeeAssignments[]->.senateCommitteeName, reelectionYear, cardLink, assumedOffice, cardBackImage}`);
            const type = params.type.replace(' ', ', ');
            const decodedFilter = decodeURI(params.filter);
            const preFilter = decodedFilter.replace('+', ' && ');
            const filter = preFilter.replace('_', ' == ');
            let leftFilter;
            let rightFilter;
            let encodedFilter;
              
            
            //check for filter parameters
            function createFilter(){
                if(params.filter) {
                    leftFilter = `[_type in [${type}] && ${filter}]`;
                } else {
                    leftFilter = `[_type in [${type}]]`;
                }
                return leftFilter;
            };
            
            createFilter();


            //check for sorting parameters, then create encoded filter
            function createSort() {
                if(params.sort) {
                    const sort = params.sort.replace('_', ' ')
                     rightFilter = `order(${sort})`;
                     encodedFilter = encodeURIComponent(`${leftFilter} | ${rightFilter}`);
                     console.log(`${leftFilter} | ${rightFilter}`)
                } else {
                     encodedFilter = encodeURIComponent(leftFilter);
                     console.log(`${leftFilter}`)
                }
                return encodedFilter;
            }

            createSort();
         


            //fetch data & call the createCard function
            fetch(`https://${projectId}.<http://api.sanity.io/${apiDate}/data/query/${dataSet}?query=*${encodedFilter}{${encodedProjection}}`)|api.sanity.io/${apiDate}/data/query/${dataSet}?query=*${encodedFilter}{${encodedProjection}}`)>
            .then(res => res.json())
            .then(cardData => cardData.result.forEach((item)=>{
                    cardGrid.append(createCard(item));
                }),
                main.append(cardGrid));
I need to make it so that multiple sorts can be chained, but I believe that's the only part left for this. I am just starting to write the sort/filter button code, but I've never done it before
😬
Awesome! I'll play around with this and give you any feedback I have!
wonderful! Have a great day
You as well!
BTW I was looking into the webhook solution for netlify, and I found a plugin on the Sanity Exchange that already takes care of this! I've not tried it yet...

https://www.sanity.io/plugins/sanity-plugin-dashboard-widget-netlify

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?