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 Dhlparcel Laravel Package

mvdnbrk/laravel-dhlparcel

Integrates the DHL Parcel API into Laravel so you can create shipments, generate shipping labels, and track parcels directly from your app via a clean, fluent, and testable interface that makes adding DHL Parcel delivery options simple.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require mvdnbrk/laravel-dhlparcel
php artisan vendor:publish --provider="Mvdnbrk\DhlParcel\DhlParcelServiceProvider"

The package is auto-discovered; publishing gives you config/dhlparcel.php. Open that file first — it is the single source of truth for credential key names, base URL, and any default label options. Expect environment-driven values along the lines of:

DHL_PARCEL_USER_ID=your-user-id
DHL_PARCEL_API_KEY=your-api-key

DHL Parcel's API authenticates with a userId + apiKey header pair rather than OAuth, so there is no token dance to manage — which also means a missing/incorrect key fails immediately with a 401 rather than a silent degradation. Get sandbox credentials from the DHL Parcel developer portal and verify the base URL in the config points at the sandbox host before you write a line of business logic.

Where to look first

  1. config/dhlparcel.php — credential names and endpoint host.
  2. The package's src/ facade + client class — this is a thin wrapper, so the method signatures there are the documentation. Read them before trusting any blog post.
  3. The DHL Parcel API reference for payload shapes; the wrapper generally passes arrays/objects through, so field names, units, and required-address rules come from DHL, not from this package.

First use case: label on order fulfilment

The canonical integration is "admin marks order as shipped → create a DHL shipment → store the label PDF → attach tracking number to the order." Prove the loop end-to-end with a hardcoded test order before touching your domain model:

use Mvdnbrk\DhlParcel\Facades\DhlParcel; // verify exact facade namespace in src/

$shipment = DhlParcel::createShipment([
    'receiver'  => [
        'name'    => 'Test Receiver',
        'address' => ['countryCode' => 'NL', 'postalCode' => '1012AB', 'city' => 'Amsterdam', 'street' => 'Dam', 'number' => '1'],
    ],
    'pieces'    => [['weight' => 1000, 'dimensions' => ['length' => 200, 'width' => 150, 'height' => 100]]],
]);

Then immediately fetch the label for that shipment and confirm you get bytes back, not an error envelope. That single round trip surfaces 90% of the integration pain (units, address validation, auth, label options).

Implementation Patterns

Wrap it, don't sprinkle it

Never call the facade directly from controllers. Put a DhlParcelService (or a CreateDhlShipment action) between your app and the package. The package is small, unmaintained-ish, and its array payloads will change shape over time; one wrapper class means one place to patch when DHL revises an endpoint or you swap to the official SDK.

final class DhlParcelService
{
    public function createLabelForOrder(Order $order): ShipmentLabel
    {
        $response = DhlParcel::createShipment($this->mapOrderToPayload($order));
        $label = DhlParcel::getLabel($response->id, ['format' => 'pdf', 'printer' => 'A4']);

        $path = "labels/{$order->id}.pdf";
        Storage::disk('s3')->put($path, base64_decode($label->pdf)); // verify response field

        return new ShipmentLabel($path, $response->trackerCode);
    }
}

Queue it, with idempotency

Creating a shipment is a non-idempotent POST. A timeout or a queue retry can produce two physical labels and two billable shipments. Guard with a dhl_shipment_id column on the order and short-circuit if it is already set; only retry on connection failures, never on 5xx responses you haven't inspected.

public function handle(Order $order): void
{
    if ($order->dhl_tracker_code) {
        return; // already created — do not re-POST
    }
    // ...create + persist within a transaction on your side
}

Persist the tracker code, stream the label

Store the barcode/tracker code on the order row so tracking lookups never depend on the DHL API. Store the PDF on a filesystem disk and keep only the path in the DB — base64 blobs in MySQL will bloat your backups fast.

Tracking sync as a scheduled command

// app/Console/Commands/SyncDhlTracking.php
Schedule::command('dhl:sync-tracking')->hourly()->withoutOverlapping();

Iterate recent shipments, call the tracker endpoint, and write a status transition only when it changes. DHL tracking responses are eventually consistent and often repeat the same status for days — diff before you fire customer notifications, or you'll email people five times about "in transit."

Testing

The facade is bound in the container, so swap the underlying client in tests rather than hitting the network:

$this->mock(DhlParcelClient::class, function ($mock) {
    $mock->shouldReceive('createShipment')->once()->andReturn((object) ['id' => 'abc', 'trackerCode' => '3SDHL123']);
});

If the package uses Laravel's HTTP client internally (check src/), Http::fake() with a recorded fixture response is the cleanest route and keeps the payload-mapping code under test too. Add at least one fixture of a failed label request — the error body is where the useful validation messages live.

Multi-tenant / per-country credentials

The service provider typically binds a singleton configured from config/dhlparcel.php. If you ship for multiple legal entities or need different DHL accounts per market, don't fight the singleton: instantiate a fresh client with an explicit credential array and bind it per-request, or resolve a tenant-aware factory. Test this early — retrofitting it after launch is painful.

Gotchas and Tips

It's a 2020 package — pin and plan to fork

Last release August 2020, ~18 stars. Check composer.json for the supported Laravel/PHP range before assuming it installs on your stack; on Laravel 10/11 you will likely need "mvdnbrk/laravel-dhlparcel": "^1.0" with a config platform override, or a fork. It's a thin wrapper (a few hundred lines), so forking is genuinely cheap — treat "vendor it into app/Services/DhlParcel" as a first-class option rather than a last resort.

Units are the classic bug

DHL Parcel works in grams and millimetres (weight 1000 = 1 kg). Passing kilograms without thinking produces either rejected shipments or, worse, accepted ones billed at 1/1000th the real weight. Write the conversion once in your mapper and add a test asserting 1.5 kg → 1500.

Labels come back base64-encoded

Decode before writing to disk, or you'll ship PDFs that start with JVBERi0= instead of %PDF-. Check the response field name (pdf, label, data) against the actual response — this is the sort of detail that differs across DHL endpoints.

Print format matters operationally

A4 vs 4x6 (thermal) is a real decision that changes warehouse workflow. Pick it in config, not per-call, so the warehouse printer never gets a mismatched page. ZPL output, if supported, is raw printer language — never try to open it as a PDF to "check."

Errors arrive late and are vague

Address validation and product-code problems surface at label creation, not shipment creation. A POST /shipments success does not mean a printable label exists. Design your UI so the operator sees the failure at label time and can correct the address, rather than discovering it hours later in a failed job. Log the full error body plus any request ID header — DHL support will ask for it.

Credentials in the config cache

config:cache freezes credentials. If you use per-tenant keys, never read them from config() inside a cached deployment path — resolve them at runtime. Also keep sandbox and production hosts strictly separated via an env switch; a misconfigured APP_ENV that sends test shipments to production is billable and embarrassing.

Extension points worth knowing

  • Override the container binding if you need a custom HTTP client (timeouts, retry middleware, logging).
  • Publish and edit the config for label defaults and the base URL — this is your main knob without forking.
  • Wrap the facade behind your own interface so a future migration to DHL's official SDK or a multi-carrier abstraction (ShippingProvider contract) is a one-file change.

Debugging checklist when a call fails

  1. 401 → credential header names (userId/apiKey) and sandbox vs prod host.
  2. 400 with a validation body → unit mismatch, missing countryCode, or a postcode format DHL doesn't accept (NL postcodes need 1234AB, no space).
  3. 200 but empty label → wrong endpoint; shipment created but label not requested.
  4. Timeout → check whether the shipment was actually created before retrying, or you'll double-ship.
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.
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus