PHPackages                             luizsilva-dev/laravel-shipping - 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. luizsilva-dev/laravel-shipping

ActiveLibrary[API Development](/categories/api)

luizsilva-dev/laravel-shipping
==============================

A unified shipping abstraction for Laravel supporting ShipStation, Shippo, and EasyPost.

05↓100%

Since Jul 6Compare

[ Source](https://github.com/luizsilva-dev/Laravel-Shipping)[ Packagist](https://packagist.org/packages/luizsilva-dev/laravel-shipping)[ RSS](/packages/luizsilva-dev-laravel-shipping/feed)WikiDiscussions Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel Shipping
================

[](#laravel-shipping)

[![PHP](https://camo.githubusercontent.com/187240af044d09d5b14a1d9d9ebdf3f7a993e4c7bc09bdb46b4ba661a891bf5b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d626c7565)](https://php.net)[![Laravel](https://camo.githubusercontent.com/0171c541f6433dcec81b1736e87bba993ce6d1b126d0954ece853c872cc659f7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31312532422d726564)](https://laravel.com)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

A modern Laravel package that provides a **unified shipping abstraction** for multiple shipping carriers. Inspired by Laravel's `Storage` and `Mail` systems — one clean API, multiple providers.

---

Supported Providers
-------------------

[](#supported-providers)

ProviderRatesLabelsTrackingAddress ValidationRefundCarriers**ShipStation** (API v2)✅✅✅✅✅✅**Shippo**✅✅✅✅✅✅**EasyPost**✅✅✅✅✅✅---

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

[](#requirements)

- PHP 8.2+
- Laravel 11+

---

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

[](#installation)

```
composer require luizsilva-dev/laravel-shipping
```

Publish the configuration file:

```
php artisan vendor:publish --tag=laravel-shipping-config
```

---

Configuration
-------------

[](#configuration)

Add the following variables to your `.env` file:

```
SHIPPING_DRIVER=shipstation

# ShipStation API v2
SHIPSTATION_API_KEY=your-shipstation-api-key
SHIPSTATION_SANDBOX=true

# Shippo
SHIPPO_API_KEY=your-shippo-api-key
SHIPPO_SANDBOX=true

# EasyPost
EASYPOST_API_KEY=your-easypost-api-key
EASYPOST_SANDBOX=true
```

The published `config/shipping.php`:

```
return [
    'default' => env('SHIPPING_DRIVER', 'shipstation'),

    'drivers' => [
        'shipstation' => [
            'driver'   => 'shipstation',
            'api_key'  => env('SHIPSTATION_API_KEY'),
            'sandbox'  => env('SHIPSTATION_SANDBOX', true),
            'base_url' => env('SHIPSTATION_BASE_URL', 'https://api.shipstation.com'),
        ],

        'shippo' => [
            'driver'   => 'shippo',
            'api_key'  => env('SHIPPO_API_KEY'),
            'sandbox'  => env('SHIPPO_SANDBOX', true),
            'base_url' => 'https://api.goshippo.com',
        ],

        'easypost' => [
            'driver'   => 'easypost',
            'api_key'  => env('EASYPOST_API_KEY'),
            'sandbox'  => env('EASYPOST_SANDBOX', true),
            'base_url' => 'https://api.easypost.com',
        ],
    ],
];
```

> **Sandbox mode:** When `sandbox=true`, ShipStation routes to `https://api-stage.shipstation.com`. Shippo and EasyPost use their test API keys natively (no separate URL needed — use a test key).

---

Basic Usage
-----------

[](#basic-usage)

The package registers a `Shipping` facade. You can call any provider using `driver()`:

```
use LuizSilvaDev\LaravelShipping\Facades\Shipping;

// Use the default driver (from config)
Shipping::rates($shipment);

// Or specify a driver explicitly
Shipping::driver('shipstation')->rates($shipment);
Shipping::driver('shippo')->rates($shipment);
Shipping::driver('easypost')->rates($shipment);
```

---

DTOs
----

[](#dtos)

All methods accept and return strongly-typed DTOs.

### AddressData

[](#addressdata)

```
use LuizSilvaDev\LaravelShipping\DTOs\AddressData;

$address = new AddressData(
    name: 'John Doe',
    street1: '1600 Pennsylvania Avenue NW',
    city: 'Washington',
    state: 'DC',
    postalCode: '20500',
    country: 'US',
    company: 'ACME Corp',       // optional
    street2: 'Suite 100',       // optional
    phone: '2025551234',        // optional
    email: 'john@example.com',  // optional
    isResidential: false,       // optional
);

// Or from array
$address = AddressData::fromArray([
    'name'        => 'John Doe',
    'street1'     => '1600 Pennsylvania Avenue NW',
    'city'        => 'Washington',
    'state'       => 'DC',
    'postal_code' => '20500',  // also accepts 'zip'
    'country'     => 'US',
]);
```

### ParcelData

[](#parceldata)

```
use LuizSilvaDev\LaravelShipping\DTOs\ParcelData;

$parcel = new ParcelData(
    weight: 6.0,
    length: 10.0,
    width: 8.0,
    height: 4.0,
    weightUnit: 'ounce',   // 'ounce', 'pound', 'gram', 'kilogram'
    dimensionUnit: 'inch', // 'inch', 'centimeter'
);
```

### ShipmentData

[](#shipmentdata)

```
use LuizSilvaDev\LaravelShipping\DTOs\ShipmentData;

$shipment = new ShipmentData(
    shipFrom: $fromAddress,
    shipTo: $toAddress,
    parcel: $parcel,
    carrierId: 'se-123890',         // optional: filter by carrier
    serviceCode: 'usps_priority_mail', // optional: filter by service
);
```

---

Address Validation
------------------

[](#address-validation)

```
use LuizSilvaDev\LaravelShipping\Facades\Shipping;
use LuizSilvaDev\LaravelShipping\DTOs\AddressData;
use LuizSilvaDev\LaravelShipping\Exceptions\InvalidAddressException;

$address = new AddressData(
    name: 'John Doe',
    street1: '1600 Pennsylvania Avenue NW',
    city: 'Washington',
    state: 'DC',
    postalCode: '20500',
    country: 'US',
);

try {
    $validated = Shipping::driver('shipstation')->validateAddress($address);

    echo $validated->street1;    // normalized address
    echo $validated->postalCode; // may return ZIP+4 (e.g. 20500-0003)
} catch (InvalidAddressException $e) {
    echo 'Invalid address: ' . $e->getMessage();
}
```

---

Get Shipping Rates
------------------

[](#get-shipping-rates)

```
use LuizSilvaDev\LaravelShipping\Facades\Shipping;
use LuizSilvaDev\LaravelShipping\DTOs\AddressData;
use LuizSilvaDev\LaravelShipping\DTOs\ParcelData;
use LuizSilvaDev\LaravelShipping\DTOs\ShipmentData;
use LuizSilvaDev\LaravelShipping\Exceptions\RateException;

$shipment = new ShipmentData(
    shipFrom: new AddressData(
        name: 'Warehouse',
        street1: '4301 Bull Creek Road',
        city: 'Austin',
        state: 'TX',
        postalCode: '78731',
        country: 'US',
    ),
    shipTo: new AddressData(
        name: 'Customer',
        street1: '179 N Harbor Dr',
        city: 'Redondo Beach',
        state: 'CA',
        postalCode: '90277',
        country: 'US',
    ),
    parcel: new ParcelData(
        weight: 6.0,
        length: 10.0,
        width: 8.0,
        height: 4.0,
    ),
    carrierId: 'se-123890', // optional
);

try {
    $rates = Shipping::driver('shipstation')->rates($shipment);

    foreach ($rates as $rate) {
        echo "{$rate->serviceType}: \${$rate->amount} ({$rate->deliveryDays} days)\n";
    }
} catch (RateException $e) {
    echo 'No rates available: ' . $e->getMessage();
}
```

Each `RateData` contains:

PropertyTypeDescription`rateId``string`Provider rate ID (use to buy label)`carrierId``string`Carrier account ID`carrierCode``string`Carrier code (e.g. `usps`, `ups`)`serviceCode``string`Service code (e.g. `usps_priority_mail`)`serviceType``string`Human-readable service name`amount``float`Shipping cost`currency``string`Currency code (e.g. `USD`)`deliveryDays``?int`Estimated delivery days`estimatedDeliveryDate``?string`Estimated delivery date (ISO 8601)---

Create Label
------------

[](#create-label)

```
use LuizSilvaDev\LaravelShipping\Facades\Shipping;
use LuizSilvaDev\LaravelShipping\Exceptions\LabelException;

try {
    $label = Shipping::driver('shipstation')->createLabel($shipment);

    echo $label->labelId;        // provider label ID
    echo $label->trackingNumber; // tracking number
    echo $label->labelUrl;       // PDF/PNG download URL
    echo $label->cost;           // shipping cost paid
} catch (LabelException $e) {
    echo 'Label creation failed: ' . $e->getMessage();
}
```

Each `LabelData` contains:

PropertyTypeDescription`labelId``string`Provider label ID`status``string`Label status`trackingNumber``string`Tracking number`labelUrl``string`Label download URL`cost``float`Amount charged`currency``string`Currency code`carrierId``?string`Carrier account ID`carrierCode``?string`Carrier code`serviceCode``?string`Service code`shipmentId``?string`Shipment ID`createdAt``?string`Creation timestamp---

Track Shipment
--------------

[](#track-shipment)

```
use LuizSilvaDev\LaravelShipping\Facades\Shipping;

// ShipStation: pass the label_id
$tracking = Shipping::driver('shipstation')->track('se-label-xyz789');

// Shippo: pass tracking number + carrier code
$tracking = Shipping::driver('shippo')->track('1Z999AA10123456784', 'ups');

// EasyPost: pass tracking number + optional carrier
$tracking = Shipping::driver('easypost')->track('1Z999AA10123456784', 'UPS');

echo $tracking->status;              // e.g. 'DE' (Delivered)
echo $tracking->statusDescription;  // e.g. 'Delivered'
echo $tracking->estimatedDeliveryDate;

foreach ($tracking->events as $event) {
    echo "{$event['occurred_at']}: {$event['description']} ({$event['city']}, {$event['state']})\n";
}
```

---

Refund / Void a Label
---------------------

[](#refund--void-a-label)

```
$voided = Shipping::driver('shipstation')->refundLabel('se-label-xyz789');

if ($voided) {
    echo 'Label voided successfully.';
}
```

---

List Carriers
-------------

[](#list-carriers)

```
$carriers = Shipping::driver('shipstation')->listCarriers();

foreach ($carriers as $carrier) {
    echo "{$carrier['carrier_code']}: {$carrier['friendly_name']}\n";
}
```

---

Webhook Handling
----------------

[](#webhook-handling)

```
use LuizSilvaDev\LaravelShipping\Webhooks\WebhookHandler;

// In your controller
public function handleShipStation(Request $request): JsonResponse
{
    $handler = new WebhookHandler('shipstation');

    // Optional: verify HMAC signature
    if (! $handler->verify($request, config('shipping.drivers.shipstation.webhook_secret'))) {
        abort(401, 'Invalid webhook signature.');
    }

    $event = $handler->handle($request);

    // $event['event']   => event type string
    // $event['payload'] => raw payload array

    return response()->json(['received' => true]);
}
```

---

Exception Handling
------------------

[](#exception-handling)

ExceptionWhen thrown`ShippingException`Base exception for all shipping errors`ProviderException`API-level error from a provider`InvalidAddressException`Address validation failed`RateException`No rates returned or rate fetch failed`LabelException`Label creation failed```
use LuizSilvaDev\LaravelShipping\Exceptions\ProviderException;
use LuizSilvaDev\LaravelShipping\Exceptions\ShippingException;

try {
    $label = Shipping::driver('shipstation')->createLabel($shipment);
} catch (ProviderException $e) {
    // Provider-specific error with status code
    logger()->error($e->getMessage(), $e->getContext());
} catch (ShippingException $e) {
    // General shipping error
    logger()->error($e->getMessage());
}
```

---

Adding a New Provider
---------------------

[](#adding-a-new-provider)

1. Create `src/Providers/MyCarrierProvider.php` extending `AbstractProvider`
2. Implement all methods from `ShippingProviderInterface`
3. Register in `ShippingManager`:

```
protected function createMycarrierDriver(): ShippingProviderInterface
{
    return new MyCarrierProvider($this->getDriverConfig('mycarrier'));
}
```

4. Add config entry in `config/shipping.php`:

```
'mycarrier' => [
    'driver'  => 'mycarrier',
    'api_key' => env('MYCARRIER_API_KEY'),
    'sandbox' => env('MYCARRIER_SANDBOX', true),
    'base_url' => 'https://api.mycarrier.com',
],
```

5. Use it:

```
Shipping::driver('mycarrier')->rates($shipment);
```

---

Running Tests
-------------

[](#running-tests)

```
composer install
vendor/bin/phpunit
```

---

Provider Notes
--------------

[](#provider-notes)

### ShipStation (API v2)

[](#shipstation-api-v2)

- Auth: `API-Key` header
- Sandbox URL: `https://api-stage.shipstation.com`
- Tracking uses `label_id` (not raw tracking number) via `GET /v2/labels/{label_id}/track`
- **Address validation is only available on paid plans.** Free plan accounts will receive a `ProviderException` with a clear message instead of a silent failure.
- `createLabel()` without `serviceCode` in `ShipmentData` **automatically selects the cheapest available rate**
- Rates with `error_messages` are automatically filtered out; if no valid rates remain, a `RateException` is thrown with the carrier error message.
- Docs:

### Shippo

[](#shippo)

- Auth: `ShippoToken ` header
- Use a **test token** (starts with `shippo_test_`) for sandbox — no separate URL
- Tracking: `GET /tracks/{carrier}/{tracking_number}`
- **`track()` requires `$carrierId`** as the second argument (e.g. `'ups'`, `'usps'`, `'fedex'`)
- `createLabel()` without `serviceCode` **automatically selects the cheapest available rate**
- Docs:

### EasyPost

[](#easypost)

- Auth: HTTP Basic Auth (API key as username, empty password)
- Use an **EZT (test) API key** for sandbox — no separate URL
- **Label creation requires two API calls** internally (create shipment → buy label) — this is handled transparently by the package
- `createLabel()` without `serviceCode` **automatically selects the cheapest available rate**
- Docs:

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

10

—

LowBetter than 0% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity5

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/f6bcfcae590230884024ca8a101da0e2f6d4ef7af0deb1b1abb8d2d1ce57d028?d=identicon)[luizsilva-dev](/maintainers/luizsilva-dev)

### Embed Badge

![Health badge](/badges/luizsilva-dev-laravel-shipping/health.svg)

```
[![Health](https://phpackages.com/badges/luizsilva-dev-laravel-shipping/health.svg)](https://phpackages.com/packages/luizsilva-dev-laravel-shipping)
```

###  Alternatives

[exsyst/swagger

A php library to manipulate Swagger specifications

35916.4M7](/packages/exsyst-swagger)[hubspot/api-client

Hubspot API client

24016.2M20](/packages/hubspot-api-client)[pocketmine/bedrock-protocol

An implementation of the Minecraft: Bedrock Edition protocol in PHP

172445.0k17](/packages/pocketmine-bedrock-protocol)[botman/driver-telegram

Telegram driver for BotMan

93459.5k6](/packages/botman-driver-telegram)

PHPackages © 2026

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