justbetter/laravel-magento-client
A Laravel package that provides a clean, fluent client for Magento 2’s REST API, making it easy to authenticate and query products, orders, customers, and other store data from your Laravel application without boilerplate.
Install the package alongside Laravel’s HTTP client:
composer require justbetter/laravel-magento-client
Publish the config (the package ships with a config file that holds the Magento base URL, store code, and auth credentials):
php artisan vendor:publish --tag=magento-config
Configure credentials in .env. Typical keys mirror Magento’s integration token flow:
MAGENTO_BASE_URL=https://shop.example.com/rest/V1
MAGENTO_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx
MAGENTO_STORE_CODE=default
Generate the token in Magento admin under System → Integrations → Add New Integration → Activate.
First use case — pull products or orders into a Laravel command/job:
use JustBetter\MagentoClient\Facades\Magento;
$products = Magento::products()->all(['searchCriteria' => ['pageSize' => 100]]);
Where to look first: open the published config/magento.php file and the package’s src/Resources (or equivalent) directory — the resource classes expose the actual API endpoints wrapped for you. Read one resource end-to-end before writing new code so you understand the request/response shape.
Wrap the facade in your own service. Don’t sprinkle Magento:: across controllers. Create app/Services/Magento/ProductService.php that depends on the client, so you can swap the implementation and test against an interface.
class ProductSyncService {
public function __construct(private Magento $magento) {}
public function sync(int $page = 1) {
return $this->magento->products()->all([
'searchCriteria' => ['pageSize' => 200, 'currentPage' => $page],
]);
}
}
Sync via queued jobs, not request lifecycle. Magento REST calls are slow and prone to timeouts. Dispatch a job per page or per entity, use ->onQueue('magento'), and back off on failure:
class ImportOrders implements ShouldQueue {
public int $tries = 3;
public array $backoff = [10, 30, 60];
public function handle(ProductSyncService $svc) { /* ... */ }
}
Cache aggressively for read-only lookups. Product/category data changes infrequently. Use Cache::remember("magento.product.{$sku}", now()->addMinutes(10), fn () => $magento->products()->get($sku)). Combine with an invalidation job triggered by a webhook if you have one.
Normalize responses into your own DTOs. Magento returns deeply nested items[] with inconsistent casing. Map into Laravel-friendly models/collections in your service, so the rest of the app never sees raw arrays:
return collect($response['items'])->map(fn ($item) => new ProductData(
sku: $item['sku'],
name: $item['name'],
price: (float) $item['price'],
));
Handle pagination explicitly. Magento’s searchCriteria uses currentPage/pageSize and returns total_count in search_criteria. Write a generator that keeps fetching until count($items) < pageSize:
foreach ($svc->paginate() as $product) { /* ... */ }
Test with Http::fake(). The package builds on Laravel’s HTTP client, so you can fake Magento responses in your tests without network calls — key for a fast CI pipeline.
Use store-scope headers for multi-store setups. If you run multiple storefronts, pass the store code (or store header) per request rather than relying only on the global config.
Token expiry. Magento integration tokens usually don’t expire unless revoked, but admin-issued tokens can. Catch 401/403 responses, log them, and surface a clear exception instead of silently returning empty data. Consider a MagentoAuthException.
Base URL shape matters. Magento REST expects .../rest/V1. A trailing slash or missing version segment produces confusing 404s. Validate in a boot check or a php artisan magento:health command.
HTTP timeouts default too low. Magento on shared hosting often takes 5–30s for larger product/order queries. Set a per-request timeout (e.g. ->timeout(60)) and avoid pulling full catalogs synchronously in web requests.
Silent API-level errors. Magento sometimes returns 200 with an error payload ({"message": "...", "parameters": ...}) rather than a non-2xx status. Check for a message key in responses and throw accordingly.
Search criteria typos. The searchCriteria array is easy to mis-shape — filters need filter_groups[].filters[] with field, value, condition_type. Build a small SearchCriteria helper instead of hand-writing arrays everywhere.
Debugging. If a call fails, enable Laravel’s HTTP client logging or temporarily dump Http::response from the client wrapper. Check the resolved base URL (config('magento.base_url')) first — config caching hides .env changes:
php artisan config:clear
Pagination edge case. When total_count is a multiple of pageSize, some implementations return one extra empty page. Guard against count($items) === 0 before recursing.
Extension points. If the package exposes a MagentoClient contract or a manager that resolves resources, you can add custom resources by extending the manager in a service provider. Look for a singleton/bind in the package’s ServiceProvider — that’s your hook for registering new endpoints (e.g., a custom Magento module’s API).
Store code consistency. If you override store per request, remember the config default also applies — mixing both on the same resource can produce cross-store data. Standardize on one approach across your service layer.
How can I help you explore Laravel packages today?