2026 Community Survey results are here! See how the Craft CMS community works. results are live!

Using Craft CMS GraphQL with Astro

How to fetch content data from Craft CMS using GraphQL.

By imple­ment­ing the sup­port for Craft’s Ele­ment API JSON data, we’ve already done a lot of the leg work for GraphQL sup­port. There are some code changes we have to make but we can reuse the same com­po­nents for fetch­ing entries that we’ve already built.

To fol­low along with this video, you will need to clone and run the house-quest-craft project. Set­ting up this project is cov­ered in the pre­vi­ous video.

Com­plete EntriesFetch.astro component:

export async function getEntries(query: string) {
    const url = "http://localhost:8888/api/";
    const response = await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            query: query,
        }),
    });
    let json = await response.json();
    return json.data.entries;
}

Com­plete EntryFetch.astro component:

---
export async function getEntry(query: string) {
    const url = "http://localhost:8888/api";
    const response = await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            query: query,
        }),
    });

    let json = await response.json();
    return json.data.entry;
}
---

Com­plete MarketNews.astro component:

---
import { getEntries } from '../components/EntriesFetch.astro';

let query = `{
            entries(sectionId: 12, limit: 3) {
                id
                title
                slug
                postDate
                ... on marketNews_default_Entry {
                    summaryText
                }
            }
        } 
        `;
let marketNewsEntries = await getEntries(query);
---

<div class="bg-secondary pt-16 pb-20 px-4 sm:px-6 lg:pt-24 lg:pb-28 lg:px-8 border-opacity-50 border-t-8 border-primary">
    <div class="relative max-w-lg mx-auto lg:max-w-7xl">
      <div>
        <h2 class="text-3xl tracking-tight font-extrabold text-gray-900 sm:text-4xl">Market News</h2>
        <p class="mt-3 text-xl text-gray-800 sm:mt-4">An expert view of what's happening in the local housing market.</p>
      </div>
      <div class="mt-6 grid gap-16 pt-12 lg:grid-cols-3 lg:gap-x-5 lg:gap-y-12">
        {marketNewsEntries.map(entry => (
        <div>
          <a href={'/news/' + entry.slug} class="block mt-4">
            <p class="text-xl font-semibold text-gray-900">{entry.title}</p>
            <p class="mt-3 text-base text-gray-900">{entry.summaryText}</p>
          </a>
        </div>
        ))}
      </div>
    </div>
  </div>

Com­plete [slug].astro component:

---
import Layout from '../../layouts/Layout.astro';
import InteriorHeader from '../../components/InteriorHeader.astro';

import { getEntry } from '../../components/EntryFetch.astro';
import { getEntries } from '../../components/EntriesFetch.astro';

const { slug } = Astro.params;

let singleArticleQuery = `{
		entry(sectionId: 12, slug: "${slug}") {
			id
			title
			slug
			... on marketNews_default_Entry {
				summaryText
				richText
			}
		}
	}
`

let entry = await getEntry(singleArticleQuery);

export async function getStaticPaths() {

    let allArticlesQuery = `
		{
			entries(sectionId: 12) {
				id
				title
				slug
				postDate
				... on marketNews_default_Entry {
					summaryText
					richText
				}
			}
		}
		`

    let entries = await getEntries(allArticlesQuery);
    return entries.map(( entry: { slug: string }) => {
        return {
            params: { slug: entry.slug},
            props: { entry: entry },
        };
    });
}

---

<Layout>
    <InteriorHeader />
    <div class="bg-white">
        <div class="relative py-16 bg-tertiary overflow-hidden">
			<div class="relative px-4 sm:px-6 lg:px-8">
				<div class="mt-6 prose prose-slate prose-lg text-gray-500 mx-auto">
                    <h1>{entry.title}</h1>
                    <div set:html={entry.richText}></div>
                    <p><a href="/news">Back to News</a></p>
                </div>
            </div>
        </div>
    </div>
</Layout>

Com­plete index.astro (home­page) component:

---
import Layout from "../layouts/Layout.astro";
import { getEntry } from "../components/EntryFetch.astro";
import MarketNews from "../components/MarketNews.astro";

let query = `
	{
		entry(section: "homepage") {
			title
			id
			... on homepage_homepage_Entry {
				richText
			}
		}
	}
`
let homepageContent = await getEntry(query);
---
<Layout title="Welcome Home">
    <div class="bg-white">
      <div class="max-w-full mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8 bg-primary shadow-2xl shadow-secondary border-opacity-50 border-b-8 border-secondary">
        <div class="text-center">
          <img
            class="mx-auto h-40 w-40 rounded-full xl:w-56 xl:h-56 shadow-xl shadow-gray-500"
            src="/images/omar-lopez-Gx5-zf_HE9w-unsplash.jpg"
            alt=""
          />
          <p
            class="mt-1 text-4xl font-extrabold text-gray-900 sm:text-5xl sm:tracking-tight lg:text-6xl"
          >
            Olivia Lopez, CRS         
          </p>
          <p class="max-w-xl mt-5 mx-auto text-xl text-gray-800">
            In a competitive market, Olivia is the <strong>experienced real estate agent</strong> you want
            on your side.
          </p>
        </div>
      </div>
      <div class="relative py-16 bg-tertiary overflow-hidden">
        <div class="relative px-4 sm:px-6 lg:px-8">
          <div class="mt-6 prose prose-indigo prose-lg text-gray-500 mx-auto">
              <div set:html={homepageContent.richText} />
          </div>
        </div>
      </div>
	  <MarketNews />
    </div>
</Layout>

Craft Version
Craft 4
Instructor
Ryan Irelan
Level
Beginner
Date Published
April 06, 2023
Ryan Irelan

I am the creator of CraftQuest, a web developer, and former software team manager. I spend most of my time improving CraftQuest with code and courses. When I'm not in front of the computer, I spend my time with my family, and running on the roads and trails of Austin, TX.