Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Http To Curl Laravel Package

jigarakatidus/laravel-http-to-curl

A Laravel package that converts HTTP client requests into ready-to-run cURL commands, making API calls easy to inspect, debug, reproduce, and share during development and testing.

View on GitHub
Deep Wiki
Context7

Getting Started

jigar-dhulla/laravel-http-to-curl turns a Laravel HTTP client request into an equivalent curl command string, so you can see (or hand to someone else) exactly what went over the wire.

1. Install

composer require jigar-dhulla/laravel-http-to-curl

The package is auto-discovered. Version 1.0.5 adds Laravel 13 support — the constraint now spans the modern Laravel range. If you are still on 10/11/12, stay on 1.0.4 only if 1.0.5's constraint somehow conflicts with your other packages; otherwise 1.0.5 is a drop-in, no-code-change upgrade. After bumping, run composer update jigar-dhulla/laravel-http-to-curl and smoke-test any code paths that call the macro.

2. Find the entry point first

Open the package's service provider and look at boot(). The whole package is almost certainly a macro registered on Illuminate\Http\Client\PendingRequest (possibly also on Factory). That single fact tells you the most important thing: the macro is available on any pending request, chained anywhere in the builder, and it sees whatever you've configured so far. Confirm the exact macro name from the README/provider before writing it into shared code — it is typically something like ->toCurl().

3. First use case: make a failing API call reproducible

The highest-value day-one use is debugging Http:: calls to third-party APIs:

$response = Http::withToken($token)
    ->withHeaders(['Accept' => 'application/json'])
    ->post('https://api.vendor.com/v1/orders', $payload);

if ($response->failed()) {
    logger()->error('Vendor API failed', [
        'status' => $response->status(),
        'body'   => $response->body(),
        'curl'   => Http::withToken($token)
            ->withHeaders(['Accept' => 'application/json'])
            ->post(/* ... */)  // same builder chain here
            ->toCurl(),        // <- confirm macro name
    ]);
}

Better: build the request once into a variable and derive both the real call and the curl string from the same builder, so they can't drift apart.

Implementation Patterns

Wrap it in a helper, don't sprinkle the macro

The macro only works before the request is sent (see Gotchas). Encapsulate it so you have one place to redact secrets and one place to change if the API shifts:

final class CurlPreview
{
    public static function for(PendingRequest $request): string
    {
        return Redactor::scrub($request->toCurl());
    }
}

Log-on-failure, not log-always

Logging every curl command in production is noisy and leaks payloads. The pattern that pays off:

  • Log the curl string at error level when $response->failed() or when a circuit breaker trips.
  • Log at debug for a specific feature flag / X-Debug header, so support can reproduce a customer's exact request.
  • Include it in exception context so Laravel's exception reporter attaches it to Sentry/Bugsnag/Flare.

Attach it to your HTTP retry/failure hooks

Laravel's client lets you observe requests globally. Register a Http::failed() / Http::beforeSending() listener in a service provider that captures the pending request's curl form into a request-scoped buffer, then flush it to the log with your own correlation ID. This gives you curl-for-everything without editing every call site.

Make bug reports self-serving

A genuinely nice integration: when an internal admin panel shows a failed integration, render the curl command in a <pre> with a copy button. Support staff can paste it straight into a terminal or Postman and confirm whether the bug is ours or the vendor's. Pair it with a note that the command contains a live token and expires.

Assert against it in tests

For SDK/client wrapper packages, you can assert that your wrapper builds the URL, headers, and body you expect without hitting the network:

$curl = MyApiClient::orders()->toCurl();

$this->assertStringContainsString('https://api.vendor.com/v1/orders', $curl);
$this->assertStringContainsString("-H 'Accept: application/json'", $curl);

This is a cheap contract test that catches accidental URL/header regressions. Prefer Http::fake() for behavioural tests and use curl-string assertions only for the shape of the request.

Pair with Http::fake() in local dev

In a local/CI environment, generate the curl string instead of sending: return the string from a fake handler so you can eyeball a whole workflow's outbound calls in one command.

Queue jobs and scheduled tasks

For jobs that call external APIs, catching the curl string in the failed-job handler (failed(Throwable $e)) is where it earns its keep — by then the PendingRequest is gone, so capture it before dispatch or inside the job body into a property you can read in failed().

Gotchas and Tips

Secrets leak by default. The generated command almost certainly includes the Authorization header, cookies, and API keys verbatim — that's the point, but it means you must never push it to a third-party log aggregator, a public Slack channel, a ticket, or an LLM without redaction. Add a scrubber that strips Authorization, Cookie, X-Api-Key, and anything matching /token|secret|password|key/i in headers and body. Treat the raw output as production credentials.

Timing: call it before send, not after. The macro lives on PendingRequest, so it reflects the request as configured at that moment. Once ->post() runs you have a Response and no curl string. That also means it captures the request before retries, redirects, and middleware mutate it — a curl string for a request that was retried three times shows only the first attempt. If you need the final URL after redirects, read it off the Response instead.

withoutVerifying() is faithfully rendered. If your code disables TLS verification, the curl output will carry -k. That's correct behaviour, but it's a great audit trip: grep the codebase for withoutVerifying and you'll find where the curl commands look scary.

Payload fidelity is best-effort. JSON bodies, form params, and query strings generally round-trip well. Multipart uploads, streamed bodies, and attach() file streams do not — expect the curl string to be incomplete or unusable for those. Same for requests already consumed by a StreamedResponse/sink. Don't build support runbooks around curl commands for file uploads; document that limitation.

Macro name collisions and version drift. Because it's a PendingRequest macro, it lives in the same namespace as your own macros (toCurl, curl, dump, etc.). If another package, or your AppServiceProvider, registers the same name, last-registered wins and you'll get baffling output. Grep your boot() methods when the output looks wrong.

Laravel 13 upgrade checklist (1.0.5). The only change is the framework constraint. After upgrading: clear config cache (php artisan config:clear), re-run your test suite (a PendingRequest internal change in a new major is exactly the sort of thing that could alter header ordering or serialisation in the output), and re-check any string assertions you wrote against the curl output — they're brittle to formatting. If you have a pinned composer.json with laravel/framework: ^12, nothing breaks, but you'll need the rest of your dependency graph on 13 to actually move.

Config publishing. If the package ships a config file (redaction, truncation, macro naming), publish it (php artisan vendor:publish --tag=...) and commit it, so environment-specific behaviour is reviewable rather than hidden in defaults. Never rely on the package default for redaction — own that file.

Extension points to watch. The useful hooks are the macro itself (wrap it), the service provider (swap the implementation via container binding if it resolves a formatter class), and the config (truncation length for huge bodies). Check whether the string is generated by a dedicated formatter class — if so, binding your own lets you enforce redaction centrally instead of hoping every call site remembers.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package