---
title: "Composables"
description: "Composables API reference"
canonical_url: "https://wpnuxt.com/reference/composables"
---
# Composables

> Composables API reference

## Return Value

All composables return:

```ts
const {
  data,           // ComputedRef<T | undefined> - The fetched data
  pending,        // Ref<boolean> - Loading state
  error,          // Ref<Error | undefined> - Error if failed
  status,         // Ref<'idle' | 'pending' | 'success' | 'error'>
  refresh,        // () => Promise<void> - Refetch data
  execute,        // () => Promise<void> - Execute query
  clear,          // () => void - Clear data and error
  transformError, // Ref<Error | undefined> - Error during data transformation
  retryCount,     // Ref<number> - Number of retries attempted
  isRetrying,     // Ref<boolean> - Whether currently retrying
} = usePosts()
```

## Options

```ts
usePosts(variables?, options?)
```

Variables can be a plain object, a `ref`, a `computed`, or a getter function. When reactive, the composable auto-refetches when the value changes.

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        lazy
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Don't block navigation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        server
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Fetch during SSR
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        immediate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Fetch immediately
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        watch
      </code>
    </td>
    
    <td>
      <code>
        any[]
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Refetch when values change
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        transform
      </code>
    </td>
    
    <td>
      <code>
        (data) => T
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Transform response
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        clientCache
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Enable client-side caching
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        getCachedData
      </code>
    </td>
    
    <td>
      <code>
        function
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Custom cache control function
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        retry
      </code>
    </td>
    
    <td>
      <code>
        boolean | number
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Enable retry with optional count
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        retryDelay
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1000
      </code>
    </td>
    
    <td>
      Delay between retries (ms)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timeout
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Request timeout (ms), 0 = disabled
    </td>
  </tr>
</tbody>
</table>

### Examples

```ts
// Client-only
usePosts(undefined, { server: false })

// Manual execution
const { execute } = usePosts(undefined, { immediate: false })
await execute()

// Reactive params (auto-refetches when params change)
const filters = reactive({ category: undefined as string | undefined })
const params = computed(() => ({
  first: 20,
  where: { categoryName: filters.category }
}))
usePosts(params)

// Watch reactive value (alternative to reactive params)
const category = ref('news')
usePosts({ categoryName: category }, { watch: [category] })

// Transform
usePosts(undefined, {
  transform: (posts) => posts?.map(p => p.title)
})

// Disable client caching for real-time data
usePosts(undefined, { clientCache: false })

// Enable retry on failure (3 attempts)
usePosts(undefined, { retry: 3 })

// Custom retry delay
usePosts(undefined, { retry: 3, retryDelay: 2000 })

// Enable timeout (10 seconds)
usePosts(undefined, { timeout: 10000 })
```

## Default Composables

### Posts

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

<tbody>
  <tr>
    <td>
      <code>
        usePosts()
      </code>
    </td>
    
    <td>
      <code>
        { limit?: number, order?: OrderEnum, orderField?: PostObjectsConnectionOrderbyEnum }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostByUri()
      </code>
    </td>
    
    <td>
      <code>
        { uri: string }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostById()
      </code>
    </td>
    
    <td>
      <code>
        { id: string }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostsByCategoryName()
      </code>
    </td>
    
    <td>
      <code>
        { categoryName: string, limit?: number, order?: OrderEnum, orderField?: PostObjectsConnectionOrderbyEnum }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePostsByCategoryId()
      </code>
    </td>
    
    <td>
      <code>
        { categoryId: number, limit?: number, order?: OrderEnum, orderField?: PostObjectsConnectionOrderbyEnum }
      </code>
    </td>
  </tr>
</tbody>
</table>

### Pages

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

<tbody>
  <tr>
    <td>
      <code>
        usePages()
      </code>
    </td>
    
    <td>
      <code>
        { first?: number }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePageByUri()
      </code>
    </td>
    
    <td>
      <code>
        { uri: string }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        usePageById()
      </code>
    </td>
    
    <td>
      <code>
        { id: string }
      </code>
    </td>
  </tr>
</tbody>
</table>

### Other

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

<tbody>
  <tr>
    <td>
      <code>
        useMenu()
      </code>
    </td>
    
    <td>
      <code>
        { name: string }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useNodeByUri()
      </code>
    </td>
    
    <td>
      <code>
        { uri: string }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useGeneralSettings()
      </code>
    </td>
    
    <td>
      —
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useViewer()
      </code>
    </td>
    
    <td>
      —
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useRevisions()
      </code>
    </td>
    
    <td>
      <code>
        { id: string }
      </code>
    </td>
  </tr>
</tbody>
</table>

### Taxonomy & Search

These are [connection queries](#connection-queries-pagination) — they return `data`, `pageInfo`, and `loadMore()`.

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

<tbody>
  <tr>
    <td>
      <code>
        useSearch()
      </code>
    </td>
    
    <td>
      <code>
        { term: string, first?: number, after?: string }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useCategories()
      </code>
    </td>
    
    <td>
      <code>
        { first?: number, after?: string, where?: RootQueryToCategoryConnectionWhereArgs }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useTags()
      </code>
    </td>
    
    <td>
      <code>
        { first?: number, after?: string, where?: RootQueryToTagConnectionWhereArgs }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        useTerms()
      </code>
    </td>
    
    <td>
      <code>
        { taxonomies?: TaxonomyEnum[], first?: number, after?: string }
      </code>
    </td>
  </tr>
</tbody>
</table>

### Navigation

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

<tbody>
  <tr>
    <td>
      <code>
        usePrevNextPost(slug)
      </code>
    </td>
    
    <td>
      Get previous/next posts for navigation
    </td>
  </tr>
</tbody>
</table>

```ts
const { prev, next } = await usePrevNextPost('my-post-slug')
// prev/next contain: { slug, uri, title }
```

### Lazy Mode

Use the `lazy: true` option for non-blocking navigation:

```ts
// Navigation happens immediately, data loads in background
usePosts({}, { lazy: true })
usePostByUri({ uri }, { lazy: true })
usePages({}, { lazy: true })
```

## Connection Queries (Pagination)

When a query includes `pageInfo` alongside `nodes`, the generated composable automatically supports pagination. It returns `data` (the nodes array), `pageInfo`, and `loadMore()`:

```ts
const {
  data,           // ComputedRef<T[] | undefined> - The items
  pageInfo,       // ComputedRef<WPPageInfo | undefined> - Cursor info
  loadMore,       // () => Promise<void> - Fetch next page and append
  pending,        // Ref<boolean>
  refresh,        // () => Promise<void> - Reset and refetch from scratch
  error,          // Ref<Error | undefined>
  status,         // Ref<'idle' | 'pending' | 'success' | 'error'>
} = await usePaginatedPosts({ first: 10 })
```

`WPPageInfo` contains:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        hasNextPage
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Whether more items exist after this page
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hasPreviousPage
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Whether items exist before this page
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        endCursor
      </code>
    </td>
    
    <td>
      <code>
        string | null
      </code>
    </td>
    
    <td>
      Cursor for fetching the next page
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        startCursor
      </code>
    </td>
    
    <td>
      <code>
        string | null
      </code>
    </td>
    
    <td>
      Cursor for fetching the previous page
    </td>
  </tr>
</tbody>
</table>

### Infinite Scroll (loadMore)

Use `loadMore()` to fetch the next page and append items to the existing list:

```ts
const { data: posts, pageInfo, loadMore, pending } = await usePaginatedPosts({ first: 10 })
// posts.value has first 10 items

await loadMore()
// posts.value now has 20 items (first + second page)

await loadMore()
// posts.value now has 30 items
```

`refresh()` resets the accumulation and re-fetches from the first page.

### Page-based Navigation

For previous/next page navigation, manage the cursor yourself with reactive params:

```ts
const after = ref<string>()
const params = computed(() => ({ first: 10, after: after.value }))
const { data: posts, pageInfo } = await usePaginatedPosts(params)

// Navigate to next page
after.value = pageInfo.value?.endCursor
```

See [Pagination](https://wpnuxt.com/guide/pagination) for complete examples of both patterns.

## Custom Composables

Create `.gql` files in `extend/queries/` to generate custom composables:

```graphql [extend/queries/FeaturedPosts.gql]
query FeaturedPosts($limit: Int = 5) {
  posts(first: $limit, where: { tag: "featured" }) {
    nodes { ...Post }
  }
}
```

Generates `useFeaturedPosts({ limit })` which you can use with the `{ lazy: true }` option when needed.

## Utilities

### getRelativeImagePath

Converts WordPress absolute image URLs to relative paths for use with NuxtImage:

```ts
const imageUrl = 'https://wordpress.example.com/wp-content/uploads/2024/01/photo.jpg'
const relativePath = getRelativeImagePath(imageUrl)
// Returns: '/wp-content/uploads/2024/01/photo.jpg'
```

Usage with NuxtImage:

```vue
<NuxtImg :src="getRelativeImagePath(post.featuredImage?.node?.sourceUrl)" />
```

### Content Type Guards

Type guards for narrowing the union type returned by `useNodeByUri()`:

```ts
const { data } = await useNodeByUri({ uri: '/about' })

if (isPage(data.value)) {
  // data.value narrowed — access page-specific fields
  console.log(data.value.isFrontPage)
}

if (isPost(data.value)) {
  // data.value narrowed — access post-specific fields
  console.log(data.value.categories)
}

// For custom post types
if (isContentType(data.value, 'event')) {
  // data.value.contentTypeName narrowed to 'event'
}
```

### unwrapScalar

Normalizes ACF select/radio fields that WPGraphQL returns as arrays regardless of single/multi configuration:

```ts
const status = unwrapScalar(event.value?.eventDetails?.eventStatus)
// string[] → first element
// string → passthrough
// null/undefined → undefined
```

### unwrapConnection

Extracts the first node from a WPGraphQL connection, useful for ACF relationship fields configured as single-value:

```ts
const venue = unwrapConnection(event.value?.eventDetails?.eventVenue)
// { nodes: [{ title: 'Main Hall' }] } → { title: 'Main Hall' }
// { nodes: [] } → undefined
// null/undefined → undefined
```

## TypeScript

Types are auto-generated from your GraphQL schema:

```ts
import type { PostFragment } from '#graphql-operations'

const { data: posts } = usePosts()
// posts: ComputedRef<PostFragment[] | undefined>
```


## Sitemap

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