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

Icecat Laravel Package

haringsrob/icecat

A Laravel wrapper around the Icecat product content API. Retrieve detailed product specifications, descriptions, images, and related data for brands and products, making it easy to enrich your catalog with Icecat's open content.

Deep Wiki
Context7

Getting Started

Install and get credentials first. The package is a thin wrapper, not a framework integration, so there is almost nothing to configure — the real setup cost is the Icecat account.

composer require haringsrob/icecat

Register for an Open Icecat account at icecat.biz (free for shops/resellers). You get a username and password; Open Icecat also typically wants a shop name for statistics. Put those in .env, never in code:

ICECAT_USERNAME=your-user
ICECAT_PASSWORD=your-pass
ICECAT_LANGUAGE=EN

Read the source before writing anything. The whole package is one small class in vendor/haringsrob/icecat/src. Open it, count the public methods (usually a product lookup by GTIN/EAN/MPN plus a brand or category lookup), and note the constructor signature and return type. Method names and whether it returns an array, an object, or raw XML/JSON vary between versions — five minutes here saves an hour of guessing. composer show haringsrob/icecat tells you the installed version; pin it in composer.json.

Wire it into the container once. There is no service provider or config file shipped, so create them:

// config/services.php
'icecat' => [
    'username' => env('ICECAT_USERNAME'),
    'password' => env('ICECAT_PASSWORD'),
    'language' => env('ICECAT_LANGUAGE', 'EN'),
],
// app/Providers/AppServiceProvider.php  (register)
$this->app->singleton(IcecatClient::class, fn () => new IcecatClient(
    config('services.icecat.username'),
    config('services.icecat.password'),
));

Adjust the constructor call to match what you read in src/. In practice most developers end up binding their own wrapper class, not the package class directly (see below).

First use case: enriching a catalog. You have a products table with an ean column and no copy, images, or specs. Run one product through the API in php artisan tinker, dump the response, and map the fields you actually need — title, brand, description, image URLs, spec key/value pairs.

Implementation Patterns

Wrap it, don't sprinkle it. The package class should appear in exactly one place in your codebase. Define a domain interface and one implementation:

interface ProductContentProvider
{
    public function findByGtin(string $gtin, string $locale = 'EN'): ?ProductContent;
}

final class IcecatContentProvider implements ProductContentProvider
{
    public function __construct(private IcecatClient $client) {}

    public function findByGtin(string $gtin, string $locale = 'EN'): ?ProductContent
    {
        try {
            $raw = $this->client->getProduct($gtin, $locale); // confirm method name
        } catch (\Throwable $e) {
            report($e);
            return null;
        }

        return $raw ? ProductContent::fromIcecat($raw) : null;
    }
}

This is the single highest-value decision you make with this package: it gives you a fake for tests, a seam to add caching, and an escape hatch if the package dies.

Cache hard, including misses. Icecat product data is near-static — copy, images, and specs change on the order of months. Cache hits forever keyed on gtin:locale; cache misses (product genuinely not in the catalog) for a day or a week so you don't burn quota re-asking for the same unknown EAN across a 40k-row import.

return Cache::rememberForever("icecat:{$gtin}:{$locale}", function () use ($gtin, $locale) {
    return $this->provider->findByGtin($gtin, $locale);
});

Always queue. Icecat responses take one to three seconds, occasionally much longer. A synchronous lookup in a web request will time out under any real load. Use a job with rate limiting and retries:

final class EnrichProductFromIcecat implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [10, 60, 300];
    public $middleware = [new RateLimited('icecat')];

    public function __construct(public int $productId) {}

    public function handle(IcecatContentProvider $provider): void
    {
        $product = Product::findOrFail($this->productId);
        $content = $provider->findByGtin($product->ean);

        if ($content === null) {
            $product->update(['icecat_status' => 'not_found', 'icecat_checked_at' => now()]);
            return;
        }

        $product->update([
            'name' => $product->name ?: $content->title,
            'brand' => $content->brand,
            'description' => $content->description,
            'icecat_id' => $content->icecatId,
            'icecat_status' => 'synced',
            'icecat_checked_at' => now(),
            'icecat_raw' => $content->raw,   // json column, invaluable for replay
        ]);

        $product->specs()->upsert($content->specs, ['product_id', 'key'], ['value', 'group']);
    }
}

Define RateLimited::for('icecat', ...) in AppServiceProvider and set it to whatever your account quota allows — start conservative at 30–60/minute.

Fan out with batches. For a catalog backfill, Bus::batch(Product::whereNull('icecat_checked_at')->pluck('id')->map(fn ($id) => new EnrichProductFromIcecat($id)))->dispatch(). The batch gives you progress in Horizon and a then() hook for a summary report.

Store the raw payload. A json column costs you nothing and lets you re-map with a local artisan command when you discover Icecat exposes a spec group you weren't capturing, without re-hitting the API.

Fallback chain on lookup. GTIN first; if that returns nothing, try brand + manufacturer part number (many older or private-label SKUs are only indexed that way); then give up and mark the row. Log the tier that succeeded — it tells you how dirty your EAN data is.

Add an admin action. A "Re-fetch from Icecat" button in Filament/Nova that dispatches the same job is the feature your merchandisers will actually use daily, more than the import.

Testing. If the package uses Guzzle under the hood, inject a Client with a MockHandler in tests, or simply bind a fake ProductContentProvider — the latter is why the wrapper exists. Note that Http::fake() will not intercept anything if the package uses raw curl or its own Guzzle client; check src/ before assuming.

Gotchas and Tips

  • Quotas are real and enforced. Open Icecat limits requests per day per account, and hammering it gets you throttled or locked out. Batch jobs plus caching plus negative-caching is not optional at scale.
  • Slow, and sometimes very slow. Set a generous timeout on the HTTP client (10–20s, not 2s) and treat a timeout as retryable, not as "product doesn't exist." Distinguishing the two is the difference between a clean re-run and a catalog full of false negatives.
  • GTIN normalization bites everyone. UPC-A is 12 digits, EAN-13 is 13 — prepend a zero. Excel eats leading zeros. Pack sizes and case codes get mixed into SKU columns. Normalize and validate the checksum before sending; Icecat won't tell you the EAN was malformed, it'll just return nothing.
  • Language is a parameter, not a translation layer. Requesting EN gives you the English record; specs and descriptions genuinely differ per locale. If you're multi-market, cache and store per-locale rather than syncing once in English and calling it done.
  • Partial records are normal. Brand missing, images missing, description in three fragments, spec values as units-only strings. Every field on your mapping object should be nullable, and never let a missing image break an import.
  • Brand strings are Icecat's, not yours. Their brand normalization won't match your internal taxonomy — keep your own brand entity and store Icecat's brand as a source field, not as the canonical name.
  • No Laravel integration shipped. No facade, no config, no provider, and it likely doesn't use Laravel's HTTP client, so retry/backoff/logging middleware you'd normally get from Http:: isn't there. Wrap it yourself rather than editing vendor/.
  • Low bus factor. 17 stars, one author, no meaningful ecosystem. Pin the version in composer.json ("haringsrob/icecat": "^1.0"), keep the wrapper interface, and accept that you may fork. The good news: the actual work is one POST to Icecat's API — reimplementing it with Http::asForm()->post() is an afternoon, and that is your real insurance policy.
  • Respect the content terms. Open Icecat is free for qualifying shops on the condition you're using it to sell, and it has attribution and redistribution rules. Don't dump raw Icecat content into a public API or a shared dataset.
  • Mirror the images. Image URLs point at Icecat's CDN. Hotlinking works but ties your product pages' uptime to their infrastructure and their terms. Download to your own disk in the job, store the path, and keep the source URL for re-sync.
  • Debugging: log the full request URL and response status on failure (redact the password), and keep icecat_raw so you can replay mapping logic offline with an artisan command instead of re-requesting.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin