---
title: "Fetching Data"
description: "Using WPNuxt composables"
canonical_url: "https://wpnuxt.com/guide/fetching-data"
---
# 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](https://wpnuxt.com/getting-started/how-it-works).

## Basic Usage

```vue
<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

```ts
// 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'
})

// Custom ordering (defaults: DATE / DESC)
const { data: sorted } = await usePosts({ orderField: 'TITLE', order: 'ASC' })
```

The `order` and `orderField` variables are available on `usePosts()`, `usePostsByCategoryName()`, and `usePostsByCategoryId()`. Possible values for `orderField`: `DATE`, `TITLE`, `MODIFIED`, `AUTHOR`, `COMMENT_COUNT`.

## Reactive Params

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

```vue
<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:

```vue
<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

```ts
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

<table>
<thead>
  <tr>
    <th>
      Scenario
    </th>
    
    <th>
      Options
    </th>
    
    <th>
      Why
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Page content (blog post, page)
    </td>
    
    <td>
      default (no options)
    </td>
    
    <td>
      Blocks navigation, best for SEO
    </td>
  </tr>
  
  <tr>
    <td>
      Sidebar / secondary content
    </td>
    
    <td>
      <code>
        lazy: true
      </code>
    </td>
    
    <td>
      Loads in background, doesn't block navigation
    </td>
  </tr>
  
  <tr>
    <td>
      Client-only interactive widget
    </td>
    
    <td>
      <code>
        server: false
      </code>
    </td>
    
    <td>
      Skips SSR, saves server resources
    </td>
  </tr>
  
  <tr>
    <td>
      Form-triggered fetch
    </td>
    
    <td>
      <code>
        immediate: false
      </code>
    </td>
    
    <td>
      Fetches only when <code>
        execute()
      </code>
      
       is called
    </td>
  </tr>
  
  <tr>
    <td>
      Content that depends on route
    </td>
    
    <td>
      <code>
        watch: [() => route.path]
      </code>
    </td>
    
    <td>
      Auto-refetches on navigation
    </td>
  </tr>
  
  <tr>
    <td>
      Filtered listing with reactive UI
    </td>
    
    <td>
      <code>
        computed(() => ({ where: ... }))
      </code>
      
       as params
    </td>
    
    <td>
      Auto-refetches when filters change
    </td>
  </tr>
</tbody>
</table>

<callout type="info">

See the [Query Options demo](https://github.com/wpnuxt/wpnuxt/blob/main/playgrounds/full/app/pages/query-options.vue) in the full playground for an interactive example of how each option affects data fetching.

</callout>

## Refreshing Data

```ts
const { data, refresh } = usePosts()

// Manually refetch
await refresh()
```

## Available Composables

<table>
<thead>
  <tr>
    <th>
      Composable
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        usePosts()
      </code>
    </td>
    
    <td>
      All posts (default limit: 10, ordered by date descending)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostByUri({ uri })
      </code>
    </td>
    
    <td>
      Single post by URI
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostById({ id })
      </code>
    </td>
    
    <td>
      Single post by ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostsByCategoryName({ categoryName })
      </code>
    </td>
    
    <td>
      Posts in category by name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostsByCategoryId({ categoryId })
      </code>
    </td>
    
    <td>
      Posts in category by ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePages()
      </code>
    </td>
    
    <td>
      All pages (default limit: 10)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePageByUri({ uri })
      </code>
    </td>
    
    <td>
      Single page by URI
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePageById({ id })
      </code>
    </td>
    
    <td>
      Single page by ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useNodeByUri({ uri })
      </code>
    </td>
    
    <td>
      Any content type by URI
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useMenu({ name })
      </code>
    </td>
    
    <td>
      Navigation menu by name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useGeneralSettings()
      </code>
    </td>
    
    <td>
      Site title, description, URL, and language
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useViewer()
      </code>
    </td>
    
    <td>
      Current authenticated user (requires <a href="https://wpnuxt.com/auth">
        @wpnuxt/auth
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useRevisions()
      </code>
    </td>
    
    <td>
      Post revisions
    </td>
  </tr>
</tbody>
</table>

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

## Related Pages

- **Quick Start Tutorial** — Build a blog from scratch using these composables
- **Custom Queries** — Create your own composables for custom data
- **Pagination** — Cursor-based pagination with loadMore and page navigation
- **Caching** — Control how responses are cached
- **How WPNuxt Works** — Understand the composable generation pipeline


## Sitemap

See the full [sitemap](https://wpnuxt.com/sitemap.md) for all pages.
