SR
Click to open github profile
Get Started

Manual Setup

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


This guide is for developers who want to integrate the Query API into an existing Nuxt 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 Nuxt 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. Nuxt App

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

npm create nuxt frontend

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

Installation and Folder Structure

First, install the @query-api/nuxt SDK in your Nuxt project.

npm install @query-api/nuxt

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

├── app
   ├── components
   ├── content
   ├── BlockHeadline.vue
   ├── ViewHome.vue
   ├── pages
   └── [...slug].vue
   ├── types
   └── base.ts
   └── app.vue
├── .env
├── nuxt.config.ts

Environment Variables

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

.env
# Allows Node.js to connect to local development URLs (e.g., DDEV).
# Remove this in production.
NODE_TLS_REJECT_UNAUTHORIZED=0

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

# The bearer token you generated in the Query API plugin settings.
NUXT_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.

Query API Configuration

We can configure the Query API in the nuxt.config.ts file.

nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2025-05-15',
  devtools: { enabled: true },
  modules: ['@query-api/nuxt'],

  craftcms: {
    baseUrl: process.env.NUXT_CRAFT_BASE_URL ?? '',
    authToken: process.env.NUXT_CRAFT_AUTH_TOKEN ?? '',
    debug: false,
    siteMap: [
      {
        handle: 'en',
        path: '/',
        origin: 'http://localhost:3000',
        id: 1,
      },
      {
        handle: 'de',
        path: '/de',
        origin: 'http://localhost:3000/de',
        id: 2,
      },
      {
        handle: 'es',
        path: '/es',
        origin: 'http://localhost:3000/es',
        id: 3,
      },
    ],
  },
})

Content Driven Components

These are the Vue 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.vue
<script setup lang="ts">
import type { CraftEntryTypeHeadline } from '~/types/base'

const props = defineProps<CraftEntryTypeHeadline>()

// This component renders a headline from a Matrix block.
// The props are fully typed based on the generated `base.ts` file.
// The `selectHeadlineTag` field is a dropdown in Craft,
// allowing content editors to choose the HTML tag (e.g., h1, h2).
</script>

<template>
  <component :is="props.selectHeadlineTag.value">
    {{ props.title }}
  </component>
</template>

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

components/content/ViewHome.vue
<script setup lang="ts">
import type { CraftPageHome } from '~/types/base'

const props = defineProps<CraftPageHome>()
</script>

<template>
  <div>
    <!-- `translatablePlainText` is a field from our Craft entry. -->
    <h1>{{ props.translatablePlainText }}</h1>
    <RichText v-if="props.richText" :text="props.richText" />
    <!-- The CraftArea component dynamically renders entries of matrix blocks -->
    <CraftArea v-if="props.contentBuilder" :content="props.contentBuilder" />
  </div>
</template>

Root Entry Point

Next let's create some NuxtLinks in the app.vue file. This will help to test, if everything works on both client and server side navigation.

app.vue
<template>
  <div>
    <ul>
      <li><NuxtLink href="/">Home</NuxtLink></li>
      <li><NuxtLink href="/de">Home DE</NuxtLink></li>
      <li><NuxtLink href="/es">Home ES</NuxtLink></li>
    </ul>
    <NuxtPage />
  </div>
</template>

Catch-All Route

This dynamic route is the core of the page rendering logic. It captures every incoming URL, fetches the corresponding entry from Craft CMS, and renders it using the CraftPage component.

pages/[...slug].vue
<script setup lang="ts">
import type { ContentMapping } from '@query-api/nuxt'
import { CraftNotImplemented } from '#components'
import ViewHome from '~/components/content/ViewHome.vue'
import BlockHeadline from '~/components/content/BlockHeadline.vue'
import type { CraftPageBase } from '~/types/base'

const uri = useCraftUri()
const currentSite = useCraftCurrentSite()

const { data: entry, error } = useCraftEntry<CraftPageBase>()
  .uri(uri.value)
  .siteId(currentSite.value.id)
  .one()

if (error.value) {
  throw new Error(error.value.message)
}

const contentMapping: ContentMapping = {
  pages: {
    home: ViewHome,
    'news:news': CraftNotImplemented,
  },
  components: {
    headline: BlockHeadline,
    text: CraftNotImplemented,
    imageAndText: CraftNotImplemented,
    image: CraftNotImplemented,
  },
}
</script>

<template>
  <CraftPage v-if="entry" :content="entry" :config="contentMapping" />
</template>

With this setup, navigating to any page on your Nuxt 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