Talk to any API without ever configuring an HTTP client.

Fetch brings the fetch() you already know from JavaScript to PHP, with axios-style responses, JSON handled for you, and nothing to set up. In Leaf, Laravel, Slim, WordPress, or a single .php file.

app.php

$res = fetch([
    'method' => 'GET',
    'url'    => 'https://api.todos.dev/users/1',
    'data'   => [],
]);

// $res->data powers the app —>
todos.app
GET · 200
M

Mika Field

Maker · Accra 🇬🇭

12 todos 4 done pro plan

"Shipping something small every day."

Mika's todos

+ 201 Created
Water the monstera
Refactor the router
Reply to Discord
Ship the fetch site 🚀

"I just want the data."

Every HTTP call in PHP starts the same way: pick a client, construct it, configure it, remember its option names, decode the JSON yourself. Fetch skips the ritual. One function, familiar from the browser, that returns your data already decoded, because calling an API should be the easiest line in your codebase, not a design decision.

No framework required

It runs in whatever you're building.

Fetch depends on exactly one thing, ext-curl — so it drops into any codebase without touching your dependency tree.

Leaf
Laravel
Symfony
Slim
WordPress
CodeIgniter
Plain PHP
+ your stack

Familiar by design

The API you've been using for years.

If you've written JavaScript, you already know Fetch. A URL gets you a GET. Shortcut methods cover the rest of HTTP. No client objects, no factories, a function you call.

Learn more →

shortcuts.php

// a url is a GET
$todos = fetch('https://api.todos.dev/todos');

// everything else reads exactly how you'd guess
fetch()->post('/todos', ['title' => 'Ship fetch site']);
fetch()->put('/todos/1', ['done' => true]);
fetch()->patch('/todos/1', ['title' => 'Shipped!']);
fetch()->delete('/todos/1');

When you need more

One config array, axios-style.

The moment a request outgrows a one-liner, pass an array instead. Method, URL, body, headers, the same shape axios taught a generation of developers, so nothing needs re-learning.

Learn more →

request.php

$response = fetch([
    'method'  => 'PUT',
    'url'     => 'https://api.todos.dev/todos/1',
    'data'    => [
        'title' => 'Take a break',
        'done'  => true,
    ],
    'headers' => ['X-Request-Id' => 'abc-123'],
]);

$response->status;  // 200
$response->data;    // decoded body

Bodies, forms & queries

Send arrays. Fetch does the encoding.

Request bodies go out as JSON automatically. Need a classic form post? Set the content type and the same array ships urlencoded. On GET requests, your data becomes the query string, nested arrays included.

Learn more →

encoding.php

// arrays post as JSON — no json_encode in sight
fetch()->post('/orders', ['sku' => 'leaf-tee', 'qty' => 2]);

// same array, classic form encoding
fetch([
    'method'  => 'POST',
    'url'     => '/legacy/checkout',
    'data'    => ['sku' => 'leaf-tee'],
    'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
]);

// on GET, data becomes ?page=2&tags[0]=php
fetch(['url' => '/posts', 'data' => ['page' => 2, 'tags' => ['php']]]);

Batteries where it counts

Everything around the request, handled.

The details that usually push you back to a heavyweight client, already covered, still one function.

Auth in one key

Basic auth is config, bearer is a header. No plugins.

'auth' => ['username' => ...,

         'password' => ...]

Base URLs

Point Fetch at your API once, every call after is a path.

Fetch::baseUrl('https://api.todos.dev');

fetch()->get('/todos');

One response shape

Data, status, headers and the original request, always there.

$res->data   $res->status

$res->headers $res->request

Raw when you want it

JSON is decoded by default, flip one flag for the raw body.

'rawResponse' => true

Full curl access

An escape hatch to every curl option, when you need the metal.

'curl' => [CURLOPT_REFERER => ...]

Your AI already knows it

Assistants have seen millions of fetch() calls, they write correct requests on the first try. One dependency (ext-curl), zero surprises.

Your next API call is one line away.

install

composer require leafs/fetch

# or: leaf install fetch