npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cardbrother/up-fetch

v1.3.6-beta.7

Published

Advanced fetch client builder for typescript.

Readme

upfetch is an advanced fetch client builder with standard schema validation, automatic response parsing, smart defaults and more. Designed to make data fetching type-safe and developer-friendly while keeping the familiar fetch API.

中文文档 (AI 翻译)

🚀 Try v2 Beta!

Version 2 of upfetch is now available in beta! The changes mainly impact advanced use cases, so most projects won’t require any modifications. Give it a try with:

npm i [email protected]

Check out the Migration Guide for details about changes and how to upgrade.
For a complete overview of new features, see the v2 documentation.

Table of Contents

➡️ Highlights

  • 🚀 Lightweight - 1.2kB gzipped, no dependency
  • 🔒 Typesafe - Validate API responses with zod, valibot or arktype
  • 🛠️ Practical API - Use objects for params and body, get parsed responses automatically
  • 🎨 Flexible Config - Set defaults like baseUrl or headers once, use everywhere
  • 🤝 Familiar - same API as fetch with additional options and sensible defaults

➡️ QuickStart

npm i up-fetch

Create a new upfetch instance:

import { up } from 'up-fetch'

export const upfetch = up(fetch)

Make a fetch request with schema validation:

import { upfetch } from './upfetch'
import { z } from 'zod'

const user = await upfetch('https://a.b.c/users/1', {
   schema: z.object({
      id: z.number(),
      name: z.string(),
      avatar: z.string().url(),
   }),
})

The response is already parsed and properly typed based on the schema.

upfetch extends the native fetch API, which means all standard fetch options are available.

➡️ Key Features

✔️ Request Configuration

Set defaults for all requests when creating an instance:

const upfetch = up(fetch, () => ({
   baseUrl: 'https://a.b.c',
   timeout: 30000,
}))

Check out the the API Reference for the full list of options.

✔️ Simple Query Parameters

👎 With raw fetch:

fetch(
   `https://api.example.com/todos?search=${search}&skip=${skip}&take=${take}`,
)

👍 With upfetch:

upfetch('/todos', {
   params: { search, skip, take },
})

Use the serializeParams option to customize the query parameter serialization.

✔️ Automatic Body Handling

👎 With raw fetch:

fetch('https://api.example.com/todos', {
   method: 'POST',
   headers: { 'Content-Type': 'application/json' },
   body: JSON.stringify({ title: 'New Todo' }),
})

👍 With upfetch:

upfetch('/todos', {
   method: 'POST',
   body: { title: 'New Todo' },
})

upfetch also supports all fetch body types.

Check out the serializeBody option to customize the body serialization.

✔️ Schema Validation

Since upfetch follows the Standard Schema Specification it can be used with any schema library that implements the spec.
See the full list here.

👉 With zod 3.24+

import { z } from 'zod'

const posts = await upfetch('/posts/1', {
   schema: z.object({
      id: z.number(),
      title: z.string(),
   }),
})

👉 With valibot 1.0+

import { object, string, number } from 'valibot'

const posts = await upfetch('/posts/1', {
   schema: object({
      id: number(),
      title: string(),
   }),
})

✔️ Lifecycle Hooks

Control request/response lifecycle with simple hooks:

const upfetch = up(fetch, () => ({
   onRequest: (options) => {
      // Called before the request is made, options might be mutated here
   },
   onSuccess: (data, options) => {
      // Called when the request successfully completes
   },
   onError: (error, options) => {
      // Called when the request fails
   },
}))

✔️ Timeout

Set a timeout for one request:

upfetch('/todos', {
   timeout: 3000,
})

Set a default timeout for all requests:

const upfetch = up(fetch, () => ({
   timeout: 5000,
}))

✔️ Error Handling

👉 ResponseError

Raised when response.ok is false.
Use isResponseError to identify this error type.

import { isResponseError } from 'up-fetch'

try {
   await upfetch('/todos/1')
} catch (error) {
   if (isResponseError(error)) {
      console.log(error.status)
   }
}
  • Use the parseRejected option to throw a custom error instead.
  • Use the reject option to decide when to throw.

👉 ValidationError

Raised when schema validation fails.
Use isValidationError to identify this error type.

import { isValidationError } from 'up-fetch'

try {
   await upfetch('/todos/1', { schema: todoSchema })
} catch (error) {
   if (isValidationError(error)) {
      console.log(error.issues)
   }
}

➡️ Usage

✔️ Authentication

You can easily add authentication to all requests by setting a default header:

const upfetch = up(fetch, () => ({
   headers: { Authorization: localStorage.getItem('bearer-token') },
}))

The bearer token will be retrieved from localStorage before each request.

✔️ Delete a default option

Simply pass undefined:

upfetch('/todos', {
   signal: undefined,
})

✔️ FormData

Grab the FormData from a form.

const form = document.querySelector('#my-form')

upfetch('/todos', {
   method: 'POST',
   body: new FormData(form),
})

Or create FormData from an object:

import { serialize } from 'object-to-formdata'

const upfetch = up(fetch, () => ({
   serializeBody: (body) => serialize(body),
}))

upfetch('https://a.b.c', {
   method: 'POST',
   body: { file: new File(['foo'], 'foo.txt') },
})

✔️ HTTP Agent

Since upfetch is "fetch agnostic", you can use undici instead of the native fetch implementation.

On a single request:

import { fetch, Agent } from 'undici'

const upfetch = up(fetch)

const data = await upfetch('https://a.b.c', {
   dispatcher: new Agent({
      keepAliveTimeout: 10,
      keepAliveMaxTimeout: 10,
   }),
})

On all requests:

import { fetch, Agent } from 'undici'

const upfetch = up(fetch, () => ({
   dispatcher: new Agent({
      keepAliveTimeout: 10,
      keepAliveMaxTimeout: 10,
   }),
}))

✔️ Multiple fetch clients

You can create multiple upfetch instances with different defaults:

const fetchMovie = up(fetch, () => ({
   baseUrl: "https://api.themoviedb.org",
   headers: {
      accept: "application/json",
      Authorization: `Bearer ${process.env.API_KEY}`,
   },
}))

const fetchFile = up(fetch, () => ({
   parseResponse: async (res) => {
      const name = res.url.split('/').at(-1) ?? ''
      const type = res.headers.get('content-type') ?? ''
      return new File([await res.blob()], name, { type })
   },
}))

➡️ Advanced Usage

✔️ Error as value

While the Fetch API does not throw an error when the response is not ok, upfetch throws a ResponseError instead.

If you'd rather handle errors as values, set reject to return false.
This allows you to customize the parseResponse function to return both successful data and error responses in a structured format.

const upfetch = up(fetch, () => ({
   reject: () => false,
   parseResponse: async (response) => {
      const json = await response.json()
      return response.ok
         ? { data: json, error: null }
         : { data: null, error: json }
   },
}))

Usage:

const { data, error } = await upfetch('/users/1')

✔️ Custom response parsing

By default upfetch is able to parse json and text sucessful responses automatically.

The parseResponse method is called when reject returns false. You can use that option to parse other response types.

const upfetch = up(fetch, () => ({
   parseResponse: (response) => response.blob(),
}))

💡 Note that the parseResponse method is called only when reject returns false.

✔️ Custom response errors

By default upfetch throws a ResponseError when reject returns true.

If you want to throw a custom error instead, you can pass a function to the parseRejected option.

const upfetch = up(fetch, () => ({
   parseRejected: async (response) => {
      const status = response.status
      const data = await response.json()
      return new CustomError(status, data)
   },
}))

✔️ Custom params serialization

By default upfetch serializes the params using URLSearchParams.

You can customize the params serialization by passing a function to the serializeParams option.

import queryString from 'query-string'

const upfetch = up(fetch, () => ({
   serializeParams: (params) => queryString.stringify(params),
}))

✔️ Custom body serialization

By default upfetch serializes the plain objects using JSON.stringify.

You can customize the body serialization by passing a function to the serializeBody option. It lets you:

  • restrict the valid body type by typing its first argument
  • transform the body in a valid BodyInit type

The following example show how to restrict the valid body type to Record<string, any> and serialize it using JSON.stringify:

// Restrict the body type to Record<string, any> and serialize it
const upfetch = up(fetch, () => ({
   serializeBody: (body: Record<string, any>) => JSON.stringify(body),
}))

// ❌ type error: the body is not a Record<string, any>
upfetch('https://a.b.c/todos', {
   method: 'POST',
   body: [['title', 'New Todo']],
})

// ✅ works fine with Record<string, any>
upfetch('https://a.b.c/todos', {
   method: 'POST',
   body: { title: 'New Todo' },
})

The following example uses superjson to serialize the body. The valid body type is inferred from SuperJSON.stringify.

import SuperJSON from 'superjson'

const upfetch = up(fetch, () => ({
   serializeBody: SuperJSON.stringify,
}))

✔️ Defaults based on the request

The default options receive the fetcher arguments, this allows you to tailor the defaults based on the actual request.

const upfetch = up(fetch, (input, options) => ({
   baseUrl: 'https://example.com/',
   headers: {
      // Add authentication only for protected routes
      Authorization:
         typeof input === 'string' && input.startsWith('/api/protected/')
            ? `Bearer ${getToken()}`
            : undefined,
   },
   // Add tracking params only for public endpoints
   params: {
      trackingId:
         typeof input === 'string' && input.startsWith('/public/')
            ? crypto.randomUUID()
            : undefined,
   },
   // Increase timeout for long-running operations
   timeout:
      typeof input === 'string' && input.startsWith('/export/') ? 30000 : 5000,
}))

➡️ API Reference

up(fetch, getDefaultOptions?)

Creates a new upfetch instance with optional default options.

function up(
   fetchFn: typeof globalThis.fetch,
   getDefaultOptions?: (fetcherOptions: FetcherOptions) => DefaultOptions,
): UpFetch

| Option | Signature | Description | | -------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- | | baseUrl | string | Base URL for all requests. | | params | object | The default query parameters. | | onRequest | (options) => void | Executes before the request is made. | | onError | (error, options) => void | Executes on error. | | onSuccess | (data, options) => void | Executes when the request successfully completes. | | parseResponse | (response, options) => data | The default success response parser. If omitted json and text response are parsed automatically. | | parseRejected | (response, options) => error | The default error response parser. If omitted json and text response are parsed automatically | | serializeBody | (body) => BodyInit | The default body serializer. Restrict the valid body type by typing its first argument. | | serializeParams | (params) => string | The default query parameter serializer. | | timeout | number | The default timeout in milliseconds. | | reject | (response) => boolean | Decide when to reject the response. | | ...and all other fetch options | | |

upfetch(url, options?)

Makes a fetch request with the given options.

function upfetch(
   url: string | URL | Request,
   options?: FetcherOptions,
): Promise<any>

Options:

| Option | Signature | Description | | -------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | baseUrl | string | Base URL for the request. | | params | object | The query parameters. | | parseResponse | (response, options) => data | The success response parser. | | parseRejected | (response, options) => error | The error response parser. | | schema | StandardSchemaV1 | The schema to validate the response against.The schema must follow the Standard Schema Specification. | | serializeBody | (body) => BodyInit | The body serializer. Restrict the valid body type by typing its first argument. | | serializeParams | (params) => string | The query parameter serializer. | | timeout | number | The timeout in milliseconds. | | reject | (response) => boolean | Decide when to reject the response. | | ...and all other fetch options | | |

isResponseError(error)

Checks if the error is a ResponseError.

isValidationError(error)

Checks if the error is a ValidationError.

isJsonifiable(value)

Determines whether a value can be safely converted to json.

Are considered jsonifiable:

  • plain objects
  • arrays
  • class instances with a toJSON method

➡️ Feature Comparison

Check out the Feature Comparison table to see how upfetch compares to other fetching libraries.

➡️ Environment Support

  • ✅ Browsers (Chrome, Firefox, Safari, Edge)
  • ✅ Node.js (18.0+)
  • ✅ Bun
  • ✅ Deno
  • ✅ Cloudflare Workers
  • ✅ Vercel Edge Runtime

s Share on Twitter