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 —>
Mika Field
Maker · Accra 🇬🇭
"Shipping something small every day."
Mika's todos
+ 201 CreatedEvery 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
Fetch depends on exactly one thing — ext-curl — so it drops into any codebase without touching your dependency tree.
Familiar by design
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
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
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
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.
install
➜ composer require leafs/fetch
# or: leaf install fetch