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.
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
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.
# 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
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.
"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.
// 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.
<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.
<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.
<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.
<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.