PHPackages                             sonnenglas/mydhl-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. sonnenglas/mydhl-php-sdk

ActiveLibrary[API Development](/categories/api)

sonnenglas/mydhl-php-sdk
========================

Unofficial PHP SDK for MyDHL REST API (DHL Express)

2.0.3(1w ago)737.6k↑564.3%5[1 PRs](https://github.com/sonnenglas/mydhl-php-sdk/pulls)MITPHPPHP ^8.2

Since Jan 4Pushed 1w ago3 watchersCompare

[ Source](https://github.com/sonnenglas/mydhl-php-sdk)[ Packagist](https://packagist.org/packages/sonnenglas/mydhl-php-sdk)[ RSS](/packages/sonnenglas-mydhl-php-sdk/feed)WikiDiscussions master Synced 2d ago

READMEChangelogDependencies (27)Versions (31)Used By (0)

mydhl-php-sdk
=============

[](#mydhl-php-sdk)

Unofficial PHP SDK for the **DHL Express MyDHL REST API** (currently aligned with spec **3.2.2**, April 2026).

Status: [![CircleCI](https://camo.githubusercontent.com/7a146058323d2fd24227975310a2b65a0f7ed9b815a775e82e5939c79eecb8f8/68747470733a2f2f636972636c6563692e636f6d2f67682f736f6e6e656e676c61732f6d7964686c2d7068702d73646b2f747265652f6d61737465722e7376673f7374796c653d736869656c64)](https://circleci.com/gh/sonnenglas/mydhl-api/tree/master)

> **Note:** Only the modern REST API is supported. The legacy SOAP API is not.

Requirements
------------

[](#requirements)

- PHP **^8.2**
- ext-json
- [`guzzlehttp/guzzle`](https://github.com/guzzle/guzzle) ^7.5
- [`ramsey/uuid`](https://github.com/ramsey/uuid) ^4.7

Installation
------------

[](#installation)

```
composer require sonnenglas/mydhl-php-sdk
```

Supported services
------------------

[](#supported-services)

ServiceSupported**RATING**Retrieve Rates for a one-piece Shipment✅Retrieve Rates for Multi-piece Shipments❌Landed Cost❌**PRODUCT**Retrieve DHL Express products❌**SHIPMENT**Create Shipment✅Customs / international shipments (export declaration)✅Re-download archived shipment documents✅Electronic Proof of Delivery✅Upload updated customs docs for shipment❌Upload Commercial Invoice Data for shipment❌**TRACKING**Track a single DHL Express Shipment✅Track multiple DHL Express Shipments (batch)✅**PICKUP**Create a DHL Express pickup booking request✅Cancel a DHL Express pickup booking request✅Update pickup information❌**IDENTIFIER**Allocate identifiers upfront❌**ADDRESS**Validate DHL Express pickup/delivery capability❌**INVOICE**Upload Commercial Invoice data❌**SERVICE POINTS / REFERENCE DATA**Look up servicepoints / reference data❌Design
------

[](#design)

The SDK splits responsibilities between **value objects** (immutable, validated request payloads) and **services** (thin transport that talk to DHL):

- `RateRequest`, `ShipmentRequest`, `PickupRequest`, `Pickup`, `ExportDeclaration`, … — immutable inputs, validated in their constructors.
- `RateService::getRates(RateRequest)` — returns `Rate[]`.
- `ShipmentService::createShipment(ShipmentRequest)` — returns `Shipment`.
- `TrackingService::track(...)` / `trackBatch(...)`
- `PickupService::book(...)` / `cancel(...)`
- `ImageService::getImages(...)` — re-download archived customs/waybill PDFs.
- `ProofOfDeliveryService::getProofOfDelivery(...)`

Every required field is a constructor parameter, so missing data fails at request-build time, not somewhere inside the API call.

Quick start
-----------

[](#quick-start)

```
use Sonnenglas\MyDHL\MyDHL;

$myDhl = new MyDHL(
    username: getenv('DHL_EXPRESS_USERNAME'),
    password: getenv('DHL_EXPRESS_PASSWORD'),
    testMode: true, // false → production
);
```

The base URLs are baked in:

EnvironmentURLSandbox`https://express.api.dhl.com/mydhlapi/test/`Production`https://express.api.dhl.com/mydhlapi/`> Sandbox is rate-limited to **500 calls/day per credential set**.

Usage
-----

[](#usage)

### Retrieve rates

[](#retrieve-rates)

```
use DateTimeImmutable;
use Sonnenglas\MyDHL\ValueObjects\Package;
use Sonnenglas\MyDHL\ValueObjects\RateAddress;
use Sonnenglas\MyDHL\ValueObjects\RateRequest;

$request = new RateRequest(
    accountNumber: '99999999',
    originAddress: new RateAddress(
        countryCode: 'DE',
        postalCode: '10117',
        cityName: 'Berlin',
    ),
    destinationAddress: new RateAddress(
        countryCode: 'DE',
        postalCode: '20099',
        cityName: 'Hamburg',
    ),
    package: new Package(weight: 10, height: 20, length: 10, width: 30),
    shippingDate: new DateTimeImmutable('tomorrow'),
);

$rates = $myDhl->getRateService()->getRates($request);
```

### Create a domestic shipment

[](#create-a-domestic-shipment)

```
use DateTimeImmutable;
use Sonnenglas\MyDHL\ValueObjects\Account;
use Sonnenglas\MyDHL\ValueObjects\Address;
use Sonnenglas\MyDHL\ValueObjects\Contact;
use Sonnenglas\MyDHL\ValueObjects\Incoterm;
use Sonnenglas\MyDHL\ValueObjects\Package;
use Sonnenglas\MyDHL\ValueObjects\Pickup;
use Sonnenglas\MyDHL\ValueObjects\ShipmentRequest;

$request = new ShipmentRequest(
    plannedShippingDateAndTime: new DateTimeImmutable('tomorrow 14:00'),
    productCode: 'N',
    shipperAddress: new Address(
        addressLine1: 'Karl-Liebknecht-Straße 13',
        countryCode: 'DE',
        postalCode: '10178',
        cityName: 'Berlin',
    ),
    shipperContact: new Contact(
        phone: '+49301234567',
        companyName: 'Acme Lab',
        fullName: 'John Shipper',
        email: 'shipper@example.com',
    ),
    receiverAddress: new Address(
        addressLine1: 'Hamburger Str. 1',
        countryCode: 'DE',
        postalCode: '20099',
        cityName: 'Hamburg',
    ),
    receiverContact: new Contact(
        phone: '+49401234567',
        companyName: 'Acme Hamburg',
        fullName: 'Jane Receiver',
        email: 'receiver@example.com',
    ),
    accounts: [new Account(typeCode: 'shipper', number: '123456789')],
    packages: [new Package(weight: 5, height: 20, length: 10, width: 30)],
    pickup: Pickup::notRequested(),
    incoterm: new Incoterm('DAP'),
);

$shipment = $myDhl->getShipmentService()->createShipment($request);
file_put_contents('label.pdf', $shipment->getLabelPdf());
```

### Customs / international shipments

[](#customs--international-shipments)

International shipments need `declaredValue`, an `Incoterm`, an `ExportDeclaration` with line items, and (recommended) a VAT/EORI/IOSS `RegistrationNumber`:

```
use Sonnenglas\MyDHL\ValueObjects\CustomerReference;
use Sonnenglas\MyDHL\ValueObjects\ExportDeclaration;
use Sonnenglas\MyDHL\ValueObjects\Invoice;
use Sonnenglas\MyDHL\ValueObjects\LineItem;
use Sonnenglas\MyDHL\ValueObjects\OutputImageProperties;
use Sonnenglas\MyDHL\ValueObjects\RegistrationNumber;

$request = new ShipmentRequest(
    // …same shipper / receiver / packages as above…
    productCode: 'P', // EXPRESS WORLDWIDE
    isCustomsDeclarable: true,
    incoterm: new Incoterm('DAP'),
    shipperRegistrationNumbers: [
        new RegistrationNumber(
            typeCode: RegistrationNumber::TYPE_VAT,
            number: 'DE123456789',
            issuerCountryCode: 'DE',
        ),
    ],
    customerReferences: [
        new CustomerReference(value: 'PO-12345', typeCode: CustomerReference::TYPE_BUYER_ORDER),
    ],
    declaredValue: 50.0,
    declaredValueCurrency: 'EUR',
    exportDeclaration: new ExportDeclaration(
        lineItems: [new LineItem(
            number: 1,
            description: 'Glass jar with embedded solar panel',
            price: 50.0,
            quantityValue: 1,
            quantityUnit: LineItem::UNIT_PIECES,
            manufacturerCountry: 'DE',
            netWeight: 2.0,
            grossWeight: 2.5,
            exportReasonType: LineItem::REASON_PERMANENT,
        )],
        invoice: new Invoice(
            number: 'INV-1001',
            date: new DateTimeImmutable('today'),
        ),
    ),
    outputImageProperties: new OutputImageProperties(
        printerDPI: 300,
        encodingFormat: OutputImageProperties::ENCODING_PDF,
    ),
);
```

### Track a shipment

[](#track-a-shipment)

```
$tracked = $myDhl->getTrackingService()->track('1234567890');

if ($tracked !== null) {
    echo $tracked->status, "\n";
    foreach ($tracked->events as $event) {
        echo $event->date, ' ', $event->time, ' — ', $event->description, "\n";
    }
}

// Batch — DHL accepts hundreds of waybills per call.
$tracked = $myDhl->getTrackingService()->trackBatch([
    '1234567890', '0987654321',
]);
```

### Book / cancel a courier pickup separately

[](#book--cancel-a-courier-pickup-separately)

Use this when the shipment was created with `Pickup::notRequested()` and the pickup needs to be booked (or cancelled) independently — typical when an order is cancelled hours before pickup time.

```
use Sonnenglas\MyDHL\ValueObjects\PickupRequest;
use Sonnenglas\MyDHL\ValueObjects\PickupShipmentSummary;

$booking = $myDhl->getPickupService()->book(new PickupRequest(
    plannedPickupDateAndTime: new DateTimeImmutable('+1 day 14:00'),
    accounts: [new Account('shipper', '123456789')],
    shipperAddress: $shipperAddress,
    shipperContact: $shipperContact,
    shipmentDetails: [new PickupShipmentSummary(
        productCode: 'N',
        isCustomsDeclarable: false,
        packages: [new Package(weight: 5, height: 20, length: 10, width: 30)],
    )],
    closeTime: '18:00',
    location: 'reception',
    locationType: PickupRequest::LOCATION_BUSINESS,
));

$myDhl->getPickupService()->cancel(
    dispatchConfirmationNumber: $booking->getFirstConfirmationNumber(),
    requestorName: 'John Smith',
    reason: 'wrongdate',
);
```

### Re-download archived documents (waybill, customs invoice)

[](#re-download-archived-documents-waybill-customs-invoice)

```
use Sonnenglas\MyDHL\Services\ImageService;

$documents = $myDhl->getImageService()->getImages(
    shipmentTrackingNumber: '1234567890',
    shipperAccountNumber: '123456789',
    typeCodes: [ImageService::TYPE_WAYBILL, ImageService::TYPE_COMMERCIAL_INVOICE],
    pickupYearAndMonth: '2026-05',
);

foreach ($documents as $doc) {
    file_put_contents("{$doc->typeCode}.pdf", $doc->content);
}
```

> `/get-image` does **not** return the transport label. The label is returned inline only at `createShipment` time. Save `Shipment::getLabelPdf()` then.

### Proof of Delivery

[](#proof-of-delivery)

```
$pods = $myDhl->getProofOfDeliveryService()->getProofOfDelivery(
    shipmentTrackingNumber: '1234567890',
    shipperAccountNumber: '123456789',
);
```

Full examples:

- [Check Rate](examples/checkRate.php)
- [Create Shipment](examples/createShipment.php)

Development
-----------

[](#development)

```
composer install
composer test              # unit tests (PHPUnit)
composer test:integration  # live sandbox tests (require DHL_EXPRESS_* env vars)
composer phpstan           # PHPStan level 9
composer lint              # PHP-CS-Fixer (dry run)
composer lint:fix          # PHP-CS-Fixer (apply fixes)
```

### Integration tests against the DHL sandbox

[](#integration-tests-against-the-dhl-sandbox)

Copy `tests/Integration/.env.example` and export your sandbox credentials, then:

```
DHL_EXPRESS_USERNAME=… \
DHL_EXPRESS_PASSWORD=… \
DHL_EXPRESS_ACCOUNT_NUMBER=… \
composer test:integration
```

Without these env vars the integration suite **auto-skips**, so contributor laptops and CI without secrets stay green. Each integration run consumes one or two of the daily 500 sandbox calls — keep them deliberate.

Upgrading
---------

[](#upgrading)

- **From 1.x → 2.0:** see [`UPGRADE-1.x-to-2.0.md`](UPGRADE-1.x-to-2.0.md). Most callers only need to update the `Shipment` response getters that became nullable; international shipments need the new `ExportDeclaration` / `declaredValue` arguments.
- **From 0.x → 1.0:** the fluent setter API on services was replaced by immutable Request VOs. See the 1.0 release notes.

Credits
-------

[](#credits)

Built and maintained by [Przemek Peron](mailto:przemek@sonnenglas.net).

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

59

—

FairBetter than 98% of packages

Maintenance98

Actively maintained with recent releases

Popularity35

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity73

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~67 days

Recently: every ~12 days

Total

25

Last Release

10d ago

Major Versions

0.3.2 → 1.0.02026-05-04

1.0.0 → 2.0.02026-05-04

PHP version history (2 changes)0.0.1PHP ~8.0

1.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/c01b6bd0faf6699a448bcb24a5b86999f5a770ea0f296dc7ade0842ad54836cc?d=identicon)[sonnenglas](/maintainers/sonnenglas)

---

Top Contributors

[![przemekperon](https://avatars.githubusercontent.com/u/169778?v=4)](https://github.com/przemekperon "przemekperon (55 commits)")

---

Tags

phpapiclientdhlmydhldhl-express

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sonnenglas-mydhl-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/sonnenglas-mydhl-php-sdk/health.svg)](https://phpackages.com/packages/sonnenglas-mydhl-php-sdk)
```

###  Alternatives

[openai-php/laravel

OpenAI PHP for Laravel is a supercharged PHP API client that allows you to interact with the Open AI API

3.7k9.5M89](/packages/openai-php-laravel)[resend/resend-php

Resend PHP library.

617.2M43](/packages/resend-resend-php)[mozex/anthropic-laravel

Laravel integration for the Anthropic API: facade, config publishing, install command, testing fakes, messages, streaming, tool use, thinking, and batches.

74331.3k1](/packages/mozex-anthropic-laravel)[checkout/checkout-sdk-php

Checkout.com SDK for PHP

563.6M13](/packages/checkout-checkout-sdk-php)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

273.0k](/packages/eslazarev-wildberries-sdk)[files.com/files-php-sdk

Files.com PHP SDK

2481.1k](/packages/filescom-files-php-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
