Examples

Complete, copy-paste Laravel service classes showing the full shipping lifecycle — from rate shopping to label generation, tracking, and void.

Rate shopping across all carriers

Query every registered carrier concurrently and get back a unified, sorted list of rates. Filter by service type to power different checkout tiers — ground-only, overnight, or everything.

Full service class

ShippingRateService.php
<?php namespace App\Services; use Illuminate\Support\Collection; use LaravelShipping\Common\DTOs\RateRequest; use LaravelShipping\Common\DTOs\RateResult; use LaravelShipping\Common\ValueObjects\Dimensions; use LaravelShipping\Common\ValueObjects\Package; use LaravelShipping\Common\ValueObjects\ShipFrom; use LaravelShipping\Common\ValueObjects\ShipTo; use LaravelShipping\RateShopper\RateShopper; final class ShippingRateService { public function __construct(private readonly RateShopper $shopper) {} /** * Return all available rates for a customer order, sorted cheapest first. */ public function getRates(array $destination, float $weight): Collection { $request = RateRequest::make( shipFrom: new ShipFrom( name: config('shipping.warehouse.name'), street1: config('shipping.warehouse.street'), city: config('shipping.warehouse.city'), state: config('shipping.warehouse.state'), zip: config('shipping.warehouse.zip'), ), shipTo: new ShipTo( name: $destination['name'], street1: $destination['street'], city: $destination['city'], state: $destination['state'], zip: $destination['zip'], ), packages: [ new Package($weight, new Dimensions(12, 10, 8)), ], ); // Shop all registered carriers concurrently, return cheapest first. return $this->shopper ->shop($request) ->sortByPrice() ->toCollection() ->map(fn (RateResult $rate) => [ 'carrier' => $rate->carrier(), 'service' => $rate->service()->value, 'price' => $rate->price()->amount, 'currency' => $rate->price()->currency, 'transit_days' => $rate->transitDays, 'guaranteed' => $rate->guaranteed, ]); } /** * Ground-only rates — useful for a "standard shipping" checkout flow. */ public function getGroundRates(array $destination, float $weight): Collection { $request = RateRequest::make( shipFrom: new ShipFrom( name: config('shipping.warehouse.name'), street1: config('shipping.warehouse.street'), city: config('shipping.warehouse.city'), state: config('shipping.warehouse.state'), zip: config('shipping.warehouse.zip'), ), shipTo: new ShipTo( name: $destination['name'], street1: $destination['street'], city: $destination['city'], state: $destination['state'], zip: $destination['zip'], ), packages: [ new Package($weight, new Dimensions(12, 10, 8)), ], ); return $this->shopper ->onlyServices([ 'UPS_GROUND', 'FEDEX_GROUND', 'FEDEX_HOME_DELIVERY', 'USPS_GROUND_ADVANTAGE', ]) ->shop($request) ->sortByPrice() ->toCollection(); } }

Minimal — script or artisan command

script.php
<?php use LaravelShipping\Common\DTOs\RateRequest; use LaravelShipping\Common\ValueObjects\Dimensions; use LaravelShipping\Common\ValueObjects\Package; use LaravelShipping\Common\ValueObjects\ShipFrom; use LaravelShipping\Common\ValueObjects\ShipTo; // No class needed — resolve RateShopper directly and go. $rates = app('shipping.rateshopper')->shop( RateRequest::make( shipFrom: new ShipFrom('Warehouse', '123 Main St', 'Chicago', 'IL', '60601'), shipTo: new ShipTo('Customer', '456 Oak Ave', 'Memphis', 'TN', '38118'), packages: [new Package(5.0, new Dimensions(12, 10, 8))], ) ); foreach ($rates->sortByPrice()->all() as $rate) { echo $rate->service()->value . ': $' . $rate->price()->amount . "\n"; } // Ground-only? Chain onlyServices() before shop(). $groundRates = app('shipping.rateshopper') ->onlyServices(['UPS_GROUND', 'FEDEX_GROUND', 'USPS_GROUND_ADVANTAGE']) ->shop($request) ->sortByPrice();

Generate a label

Pass a carrier key and service code, get back a tracking number and base64-encoded PDF or ZPL label. The same call works for UPS, FedEx, USPS, and DHL — only the carrier key changes.

Full service class

LabelService.php
<?php namespace App\Services; use App\Models\Order; use App\Models\Shipment; use Illuminate\Contracts\Container\BindingResolutionException; use LaravelShipping\Common\Contracts\CarrierInterface; use LaravelShipping\Common\DTOs\ShipmentRequest; use LaravelShipping\Common\DTOs\ShipmentResult; use LaravelShipping\Common\Enums\LabelFormat; use LaravelShipping\Common\ValueObjects\Dimensions; use LaravelShipping\Common\ValueObjects\Package; use LaravelShipping\Common\ValueObjects\ShipFrom; use LaravelShipping\Common\ValueObjects\ShipTo; final class LabelService { /** * Create a shipping label for an order and persist the result. * * @param string $carrierKey e.g. 'ups', 'fedex', 'usps', 'dhl' * @param string $serviceCode e.g. 'UPS_GROUND', 'FEDEX_OVERNIGHT' * * @throws BindingResolutionException */ public function createLabel(Order $order, string $carrierKey, string $serviceCode): Shipment { /** @var CarrierInterface $carrier */ $carrier = app("shipping.carrier.{$carrierKey}"); $request = new ShipmentRequest( shipFrom: new ShipFrom( name: config('shipping.warehouse.name'), street1: config('shipping.warehouse.street'), city: config('shipping.warehouse.city'), state: config('shipping.warehouse.state'), zip: config('shipping.warehouse.zip'), phone: config('shipping.warehouse.phone'), ), shipTo: new ShipTo( name: $order->recipient_name, street1: $order->shipping_street, city: $order->shipping_city, state: $order->shipping_state, zip: $order->shipping_zip, phone: $order->recipient_phone, ), packages: [ new Package( weight: $order->weight_lbs, dimensions: new Dimensions( $order->length_in, $order->width_in, $order->height_in, ), ), ], serviceCode: $serviceCode, labelFormat: LabelFormat::PDF, reference: (string) $order->id, ); /** @var ShipmentResult $result */ $result = $carrier->createShipment($request); // Persist and return the shipment record. return Shipment::create([ 'order_id' => $order->id, 'carrier' => $result->carrier, 'service' => $result->service, 'tracking_number' => $result->trackingNumbers[0] ?? null, 'shipment_id' => $result->shipmentId, 'label_data' => $result->labels[0]->data() ?? null, 'label_format' => $result->labels[0]->format()->value ?? 'PDF', 'total_charge' => $result->totalCharge, 'currency' => $result->currency, ]); } }

Minimal — script or artisan command

script.php
<?php use LaravelShipping\Common\DTOs\ShipmentRequest; use LaravelShipping\Common\Enums\LabelFormat; use LaravelShipping\Common\ValueObjects\Dimensions; use LaravelShipping\Common\ValueObjects\Package; use LaravelShipping\Common\ValueObjects\ShipFrom; use LaravelShipping\Common\ValueObjects\ShipTo; // Resolve any carrier by key — swap 'fedex' for 'ups', 'usps', or 'dhl'. $carrier = app('shipping.carrier.fedex'); // --- PDF label (default) --- $result = $carrier->createShipment(new ShipmentRequest( shipFrom: new ShipFrom('Warehouse', '123 Main St', 'Chicago', 'IL', '60601'), shipTo: new ShipTo('Customer', '456 Oak Ave', 'Memphis', 'TN', '38118'), packages: [new Package(5.0, new Dimensions(12, 10, 8))], serviceCode: 'FEDEX_GROUND', labelFormat: LabelFormat::PDF, // default — omit this line and you still get PDF )); echo $result->trackingNumbers[0]; // 123456789012 // Save to disk. file_put_contents('label.pdf', base64_decode($result->labels[0]->data())); // Return as a download response in a controller. return response(base64_decode($result->labels[0]->data())) ->header('Content-Type', 'application/pdf') ->header('Content-Disposition', 'attachment; filename="label.pdf"'); // --- ZPL label (thermal printers) --- $result = $carrier->createShipment(new ShipmentRequest( shipFrom: new ShipFrom('Warehouse', '123 Main St', 'Chicago', 'IL', '60601'), shipTo: new ShipTo('Customer', '456 Oak Ave', 'Memphis', 'TN', '38118'), packages: [new Package(5.0, new Dimensions(12, 10, 8))], serviceCode: 'FEDEX_GROUND', labelFormat: LabelFormat::ZPL, )); // ZPL is plain text — send directly to a Zebra printer or label service. $zpl = $result->labels[0]->data(); // raw ZPL string, no base64 decode needed // Send straight to a networked Zebra printer (port 9100). $socket = fsockopen('192.168.1.100', 9100); fwrite($socket, $zpl); fclose($socket);

Track a shipment

Fetch live tracking events from the carrier and get back a normalized collection of statuses. The TrackingStatusUpdated event fires automatically after every track() call — wire up a listener and you never have to poll manually.

Full service class

TrackingService.php
<?php namespace App\Services; use App\Models\Shipment; use LaravelShipping\Common\Contracts\CarrierInterface; use LaravelShipping\Common\DTOs\TrackingEvent; use LaravelShipping\Common\Enums\TrackingStatus; use Illuminate\Support\Collection; final class TrackingService { /** * Fetch the latest tracking events for a shipment and update its status. */ public function refresh(Shipment $shipment): Collection { /** @var CarrierInterface $carrier */ $carrier = app("shipping.carrier.{$shipment->carrier}"); $events = $carrier->track($shipment->tracking_number); // Persist the latest status back to the shipment record. $latest = $events->first(); if ($latest !== null) { $shipment->update([ 'tracking_status' => $latest->status->value, 'tracking_updated_at' => now(), ]); } return $events ->all() ->map(fn (TrackingEvent $event) => [ 'status' => $event->status->value, 'description' => $event->description, 'location' => $event->location, 'occurred_at' => $event->occurredAt?->toIso8601String(), ]); } /** * Check whether a shipment has been delivered. */ public function isDelivered(Shipment $shipment): bool { /** @var CarrierInterface $carrier */ $carrier = app("shipping.carrier.{$shipment->carrier}"); return $carrier->track($shipment->tracking_number)->isDelivered(); } /** * Listen for the TrackingStatusUpdated event fired automatically * after any track() call — wire this up in EventServiceProvider. * * public function handle(TrackingStatusUpdated $event): void * { * Shipment::where('tracking_number', $event->trackingNumber) * ->update(['tracking_status' => $event->status->value]); * } */ }

Minimal — script or artisan command

script.php
<?php // Resolve the carrier that created the shipment. $carrier = app('shipping.carrier.ups'); $events = $carrier->track('1Z999AA10123456784'); foreach ($events->all() as $event) { echo $event->status->value . ' — ' . $event->description . "\n"; // e.g. "IN_TRANSIT — Package transferred to destination facility" } // Simple delivery check. if ($events->isDelivered()) { echo 'Delivered!'; }

Void a label

Cancel a label before it ships. supportsFeature() lets you check carrier capability first — USPS does not support void, so you can guard against it cleanly without try/catch at the call site.

Full service class

VoidService.php
<?php namespace App\Services; use App\Models\Shipment; use LaravelShipping\Common\Contracts\CarrierInterface; use LaravelShipping\Common\Exceptions\ShipmentException; final class VoidService { /** * Void a shipment with the carrier and mark it voided locally. * * Returns true if the carrier accepted the void, false if the carrier * does not support voiding (e.g. USPS) or the shipment was not found. */ public function void(Shipment $shipment): bool { /** @var CarrierInterface $carrier */ $carrier = app("shipping.carrier.{$shipment->carrier}"); // Check before calling — USPS does not support void. if (! $carrier->supportsFeature('void')) { return false; } try { $voided = $carrier->voidShipment($shipment->shipment_id); } catch (ShipmentException $e) { report($e); return false; } if ($voided) { $shipment->update([ 'voided_at' => now(), 'tracking_status' => 'VOIDED', ]); } return $voided; } }

Minimal — script or artisan command

script.php
<?php $carrier = app('shipping.carrier.fedex'); // Guard first — USPS does not support void. if (! $carrier->supportsFeature('void')) { echo 'This carrier does not support voiding.'; return; } $voided = $carrier->voidShipment('7489175804790248980'); echo $voided ? 'Label voided.' : 'Could not void — may already be picked up.';

Ready to install?

UPS is free. FedEx, USPS, DHL, and Rate Shopper are $49 each — one-time, no subscription.