Guide

Fetching Data

Using WPNuxt composables

Without WPNuxt, fetching WordPress content means writing GraphQL queries, managing loading states, handling SSR hydration, and defining TypeScript types — all manually. WPNuxt composables handle all of this for you: one function call returns typed data with built-in caching, SSR support, and error handling.

Each composable supports both blocking and non-blocking modes via the lazy option. To understand how composables are generated from .gql files at build time, see How WPNuxt Works.

Basic Usage

<script setup lang="ts">
const { data: posts, pending, error } = await usePosts()
</script>

<template>
  <div v-if="pending">Loading...</div>
  <div v-else-if="error">{{ error.message }}</div>
  <article v-for="post in posts" :key="post.id">
    <h2>{{ post.title }}</h2>
  </article>
</template>

With Parameters

// By URI
const { data: post } = await usePostByUri({ uri: '/my-post' })

// By ID
const { data: page } = await usePageById({ id: '123' })

// With variables
const { data: posts } = await usePostsByCategoryName({
  categoryName: 'news'
})

Reactive Params

Pass a ref, computed, or getter function as variables to auto-refetch when the value changes:

<script setup lang="ts">
const category = ref<string>()

const params = computed(() => ({
  categoryName: category.value
}))

const { data: posts, pending } = usePostsByCategoryName(params)
</script>

<template>
  <select v-model="category">
    <option :value="undefined">All</option>
    <option value="news">News</option>
    <option value="tutorials">Tutorials</option>
  </select>
  <div v-if="pending">Loading...</div>
  <article v-for="post in posts" :key="post.id">
    <h2>{{ post.title }}</h2>
  </article>
</template>

No watch option needed — the composable detects the reactive source and refetches automatically. Undefined values are simply not sent in the GraphQL request, so unset filters don't affect the query.

Lazy Loading

Use the lazy: true option for non-critical content. This allows navigation to happen immediately while data loads in the background:

<script setup lang="ts">
// Navigation happens immediately, data loads in background
const { data: posts, pending } = usePosts({}, { lazy: true })
</script>

<template>
  <div v-if="pending">Loading posts...</div>
  <PostList v-else :posts="posts" />
</template>

Options

const { data } = usePosts(variables, {
  lazy: true,        // Non-blocking navigation, show loading state
  server: false,     // Skip SSR, fetch on client only
  immediate: false,  // Don't fetch until execute() is called
  watch: [slug],     // Refetch when slug changes
})

Choosing the Right Options

ScenarioOptionsWhy
Page content (blog post, page)default (no options)Blocks navigation, best for SEO
Sidebar / secondary contentlazy: trueLoads in background, doesn't block navigation
Client-only interactive widgetserver: falseSkips SSR, saves server resources
Form-triggered fetchimmediate: falseFetches only when execute() is called
Content that depends on routewatch: [() => route.path]Auto-refetches on navigation
Filtered listing with reactive UIcomputed(() => ({ where: ... })) as paramsAuto-refetches when filters change
See the Query Options demo in the full playground for an interactive example of how each option affects data fetching.

Refreshing Data

const { data, refresh } = usePosts()

// Manually refetch
await refresh()

Available Composables

ComposableDescription
usePosts()All posts (default limit: 10)
usePostByUri({ uri })Single post by URI
usePostById({ id })Single post by ID
usePostsByCategoryName({ categoryName })Posts in category by name
usePostsByCategoryId({ categoryId })Posts in category by ID
usePages()All pages (default limit: 10)
usePageByUri({ uri })Single page by URI
usePageById({ id })Single page by ID
useNodeByUri({ uri })Any content type by URI
useMenu({ name })Navigation menu by name
useGeneralSettings()Site title, description, URL, and language
useViewer()Current authenticated user (requires @wpnuxt/auth)
useRevisions()Post revisions

All composables support the { lazy: true } option for non-blocking navigation.

Copyright © 2026