The Universal Fetch — In Handlers, Stores, Server Routes
$fetch
$fetch is ofetch under the hood — it works on server and client, parses JSON automatically, and throws on non-2xx status codes.
What you'll learn
- Use $fetch from button handlers and Pinia actions
- Pass method, body, headers, and query options
- Handle errors with try/catch
useFetch and useAsyncData are for page-level data. For everything else — handlers, actions,
server routes — use $fetch.
Basic Calls
// GET
const user = await $fetch(`/api/users/${id}`)
// POST with body — auto JSON-stringified
const created = await $fetch('/api/users', {
method: 'POST',
body: { name: 'Ada' }
})
// Query string
const list = await $fetch('/api/posts', { query: { page: 2, tag: 'js' } }) Inside Click Handlers
<script setup lang="ts">
const name = ref('')
async function submit() {
try {
await $fetch('/api/users', { method: 'POST', body: { name: name.value } })
name.value = ''
} catch (err: any) {
console.error(err.data ?? err)
}
}
</script>
<template>
<input v-model="name" />
<button @click="submit">Create</button>
</template> Errors Throw
Unlike the native fetch, $fetch throws on any non-2xx response. The thrown error has a data
property containing the parsed body — useful for showing server-provided error messages.
Raw Responses
When you need headers or status, use $fetch.raw:
const res = await $fetch.raw('/api/posts')
console.log(res.status, res.headers.get('x-total-count'))
const body = res._data $fetch also works inside server/api/*.ts handlers — it has the same shape on both sides of the
wire.