SR
Click to open github profile
Get Started

Manual Setup

Learn how to install and configure the @query-api/next package for your React project.


This guide is for developers who want to integrate the Query API into an existing React project or for those who want to understand the setup process step-by-step.

Note

If you prefer to dive straight into code, you can check out the React demo project on GitHub.

Prerequisites

Before you begin, please ensure you have the following set up:

1. Craft CMS with Query API

You need a running Craft CMS instance with the Query API plugin installed and configured.

Additionally, make sure you have at least one section created in Craft CMS. For this guide, we'll assume you have a section with the handle home and an entry type also named home.

You can also use the CLI to set up a Craft-only project:

npx create-query-api@latest query-api-starter --template craft-only
Note

You can log into the control panel with these credentials:

  • Username: admin
  • Password: admin123

2. React App

You'll need a React application. If you're starting from scratch, you can create one inside your Craft project's root folder.

npm create vite@latest frontend -- --template react-ts
Note

If you set up your Craft CMS project with the starter cli craft-only template, ensure that the dev server url (https://localhost:5173) matches the WEBSITE_URL environment variable in the .env of your Craft CMS project.

You can now open the frontend directory in your code editor to begin the setup.

Installations and Folder Structure

First, install the @query-api/react SDK and react-router in your React project.

npm install @query-api/react react-router

Next, create the following folder and file structure inside your src directory. This structure helps organize your code by separating concerns.

├── src
   ├── components
   ├── content
   ├── BlockHeadline.tsx
   ├── ViewHome.tsx
   ├── libs
   └── query-api.ts
   ├── types
   └── base.ts
   ├── App.tsx
   ├── CraftRouter.tsx
   ├── main.tsx
   └── vite-env.d.ts
├── .env

Environment Variables

Create a .env file in the root of your Next.js project to store your Craft CMS connection details.

.env
# The base URL of your Craft CMS backend.
VITE_CRAFT_BASE_URL=https://query-api-starter.ddev.site

# The bearer token you generated in the Query API plugin settings.
VITE_CRAFT_AUTH_TOKEN="Bearer sqKTlMFsky_OeJVeDfnps75b2Gny4NBG" # Default of create-query-api starter template
Note

You can find/create the bearer token under /admin/query-api/tokens in the control panel.

Generate Types

The Query API plugin can generate TypeScript types based on your Craft CMS sections and entry types. This provides full type safety for your frontend data.

First, add a script to your composer.json in your Craft CMS project root.

composer.json
  "scripts": {
    "generate-types": "php craft query-api/typescript/generate-types --output=@root/frontend/src/types/base.ts"
  }

Now, run the command to generate the base.ts file. If you are using DDEV, you can run:

ddev composer run generate-types

This will create a base.ts file in frontend/src/types with all the necessary type definitions.

Content Driven Components

These are the React components that will render your Craft CMS content. We recommend placing them in a dedicated components/content directory to distinguish them from general-purpose UI components.

Here is an example of a component for a headline entry type that is used in a matrix block.

components/content/BlockHeadline.tsx
import type { CraftEntryTypeHeadline } from '../../types/base'

// This component renders a headline from a Matrix block.
// The props are fully typed based on the generated `base.ts` file.
export default function Headline(props: CraftEntryTypeHeadline) {
  // The `selectHeadlineTag` field is a dropdown in Craft,
  // allowing content editors to choose the HTML tag (e.g., h1, h2).
  const Tag = props.selectHeadlineTag.value
  return <Tag>{props.title}</Tag>
}

Next, create the main view component for your home section.

components/content/ViewHome.tsx
import { CraftArea } from '@query-api/react'
import type { CraftPageHome } from '../../types/base'

// This component renders the "Home" page.
export default function Home(props: CraftPageHome) {
  return (
    <div>
      {/* `translatablePlainText` is a field from our Craft entry. */}
      <h1>{props.translatablePlainText}</h1>

      {/* The CraftArea component dynamically renders Matrix blocks. */}
      {/* It takes the `contentBuilder` field (a Matrix field) and maps each block */}
      {/* to the corresponding component defined in `query-api.ts`. */}
      <CraftArea content={props.contentBuilder} />
    </div>
  )
}

Query API Configuration

This file initializes the Query API client and defines the contentMapping, which tells the library which React components to use for different Craft CMS pages and entry types.

libs/query-api.ts
import { craftInit, CraftNotImplemented } from '@query-api/react'

// Import the components that will render Craft CMS content.
import ViewHome from '../components/content/ViewHome'
import BlockHeadline from '../components/content/BlockHeadline'

export const craftConfig = craftInit({
  baseUrl: import.meta.env.VITE_CRAFT_BASE_URL,
  authToken: import.meta.env.VITE_CRAFT_AUTH_TOKEN,
  
  // Define a site map for multi-site setups.
  siteMap: [
    { handle: 'en', path: '/', origin: 'http://localhost:5173', id: 1 },
    { handle: 'de', path: '/de', origin: 'http://localhost:5173', id: 2 },
    { handle: 'es', path: '/es', origin: 'http://localhost:5173', id: 3 },
  ],

  // The contentMapping connects Craft handles to your React components.
  contentMapping: {
    // `pages` maps a section handle (e.g., "home") to a view component.
    pages: {
      home: ViewHome,
    },
    // `components` maps a block type handle (e.g., "headline") to a component.
    // This is typically used for Matrix blocks.
    components: {
      headline: BlockHeadline,
      // Use CraftNotImplemented as a placeholder for components you haven't created yet.
      text: CraftNotImplemented,
      imageAndText: CraftNotImplemented,
      image: CraftNotImplemented,
    }
  }
})

Router Setup

We need to use the useLocation() hook in the App later on, that's why we need to wrap the <App /> with the BrowserRouter Provider.

main.tsx
import { StrictMode } from 'react'
import { BrowserRouter } from 'react-router'
import * as ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>
)

Catch-All Component

This component will be the router for all Craft CMS pages. We basically query the data based on the current url and render the matching React Component.

CraftRouter.tsx
import {
  CraftPage,
  useCraftCurrentSite,
  useCraftEntry,
  useCraftUri,
} from '@query-api/react'
import type { CraftPageBase } from './types/base'

export default function CraftRouter() {
  const uri = useCraftUri()
  const { id: siteId } = useCraftCurrentSite()

  const { data, loading, error } = useCraftEntry<CraftPageBase>()
    .uri(uri)
    .siteId(siteId)
    .one()

  if (error) {
    console.error(error)
  }
  return <div>{!loading && data && <CraftPage content={data} />}</div>
}

App Setup

The CraftProvider must wrap your root layout to make the configuration available to all components.

src/App.tsx
import { Route, Routes, Link, useLocation } from 'react-router'
import CraftRouter from './CraftRouter'
import { CraftProvider, getCraftLocation } from '@query-api/react'
import { craftConfig } from './libs/query-api'

export function App() {
  const location = getCraftLocation(useLocation())
  return (
    <CraftProvider config={craftConfig} location={location}>
      <div>
        <nav>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/de">Home DE</Link></li>
            <li><Link to="/es">Home ES</Link></li>
          </ul>
        </nav>
        <Routes>
          <Route path="*" element={<CraftRouter />} />
        </Routes>
      </div>
    </CraftProvider>
  )
}

export default App

With this setup, navigating to any page on your React site will trigger a fetch to your Craft CMS backend, and the correct content will be rendered automatically. It's as simple as that! 🚀


Anything missing?

If you have questions, run into issues, or have ideas for improvements, your feedback is very welcome! Please don't hesitate to open an issue on GitHub. Whether it's a bug report, a feature request, or a general suggestion, your input helps make this project better for everyone.


Copyright © 2025 Samuel Reichör