xavicabot/laravel-holded
Laravel wrapper for the Holded API that lets you manage invoices, contacts, products, accounting entries and more from your PHP application. Clean, fluent syntax makes it easy to add invoicing and ERP features to any Laravel project.
isRateLimited()/isServerError() semantics let the team define failure behavior deliberately (retry, queue, surface to user) instead of guessing at Guzzle exception types. This directly supports decisions about whether Holded calls can run synchronously or must be queued.BaseModule and the 49-test suite lower the ongoing cost of ownership; that's a budget line item to weigh against a paid integration platform or in-house code.Adopt when:
Look elsewhere, or budget custom work, when:
For executives: This release moves our Holded integration from a fragile prototype to something we can put in front of customers. It adds reliability (automatic retries when Holded is busy or erroring), clear error reporting instead of cryptic failures, and two new capabilities — attaching files to documents and pulling invoice/estimate PDFs — that map directly to features customers ask for. It is tested (49 automated tests) and explicitly supports the Laravel versions we're standardizing on, so it won't hold back our framework upgrades. Cost is low and contained: one dependency, no new infrastructure, no vendor contract. The caveats are honest ones — it requires PHP 8.2+ and Laravel 11+, so we should confirm our deployment baseline, and it covers documents and contacts rather than all of Holded, so anything beyond that is scoped separately.
For engineering: Adopt v3.0 and treat the upgrade as a small migration, not a rewrite. Three things to plan for: the PHP 8.2 floor, HoldedException replacing bubbled Guzzle exceptions (any existing catch (GuzzleException) or RequestException handlers need updating, and this is actually an improvement — use isRateLimited()/isServerError()/isClientError() for branching), and the new $retryConfig constructor parameter (update any manual module instantiation, or move to container resolution). Wire retry behavior through config/holded.php per environment — conservative in request-path code, more aggressive in queued jobs. Prefer the ContactData DTO over raw arrays for contacts to get validation and normalization for free, and keep arrays only at the boundaries. Route attach() and pdf() calls through queued jobs where file sizes or latency could affect response times, and note that pdf() returns base64 — decide deliberately on decode-and-store versus stream-through. Finally, log the response body from HoldedException on 4xx so we can distinguish our own malformed payloads from Holded-side issues; the exception gives us that detail, but it needs to reach our observability stack to be worth anything.
How can I help you explore Laravel packages today?