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