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

Shortpixel Php Laravel Package

shortpixel/shortpixel-php

Official PHP SDK for ShortPixel image optimization. Compress JPEG, PNG, GIF, WebP, AVIF and PDF files via the ShortPixel API using lossy, glossy or lossless modes, with bulk processing, CDN delivery and framework-agnostic support for any PHP app, including Laravel.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is framework-agnostic — there is no service provider, facade, or publishable config. Installation is a single Composer require, and everything is driven through static options plus namespaced helper functions.

composer require shortpixel/shortpixel-php

Put the credentials in config/services.php so config:cache works correctly:

// config/services.php
'shortpixel' => [
    'key'      => env('SHORTPIXEL_API_KEY'),
    'lossy'    => (int) env('SHORTPIXEL_LOSSY', 1),   // 0 lossless, 1 lossy, 2 glossy
    'convertto'=> env('SHORTPIXEL_CONVERT', '+webp'), // '' to disable
],

Where to look first, in this order:

  1. README.md — the canonical ShortPixel::setOptions() + fromFiles() / fromUrls() / fromFolder() examples.
  2. src/ShortPixel.php — the static options registry and the entry points you will actually call.
  3. src/Result.php — the object you code against. Read every public method before writing your wrapper; this is the real API surface (isSuccess(), getSavedFile(), getOriginalFile(), getPercentImprovement(), getMessage()).
  4. src/Helper/UtilHelper.php — path normalization and option parsing, useful when things behave unexpectedly.
  5. src/ShortPixelClient.php — present in newer releases; a lower-level client if you need explicit request control or async handling instead of the static helpers.

A minimal first use case is a queued job that optimizes one uploaded image. This is the shape you will reuse everywhere:

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use ShortPixel\ShortPixel;

class OptimizeImage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 180;
    public int $tries   = 3;

    public function __construct(public string $absolutePath) {}

    public function handle(): void
    {
        ShortPixel::setOptions([
            'key'       => config('services.shortpixel.key'),
            'lossy'     => config('services.shortpixel.lossy'),
            'convertto' => config('services.shortpixel.convertto'),
            'wait'      => 30,
        ]);

        foreach (ShortPixel\fromFiles($this->absolutePath) as $result) {
            if (! $result->isSuccess()) {
                $this->fail(new \RuntimeException($result->getMessage()));
                return;
            }
            // $result->getSavedFile(), $result->getPercentImprovement()
        }
    }
}

Implementation Patterns

Wrap it in one injectable service. Because options are static/global, configure them in exactly one place and bind it as a singleton. Scattering ShortPixel::setOptions() calls across controllers is the fastest way to get inconsistent compression settings.

namespace App\Services;

use ShortPixel\ShortPixel;

class ImageOptimizer
{
    public function __construct()
    {
        ShortPixel::setOptions([
            'key'       => config('services.shortpixel.key'),
            'lossy'     => config('services.shortpixel.lossy'),
            'convertto' => config('services.shortpixel.convertto'),
            'wait'      => config('services.shortpixel.wait', 30),
        ]);
    }

    /** @return \ShortPixel\Result[] */
    public function optimize(string|array $paths): array
    {
        return iterator_to_array(ShortPixel\fromFiles($paths));
    }
}

One job per image, never inline in a request. The SDK blocks while it uploads and polls. A controller action that loops over a gallery will time out under PHP-FPM. Dispatch a job per file, keep $timeout on the job above the SDK's wait, and set Horizon/retry_after accordingly.

Backfills via fromFolder() in an Artisan command. For a one-off migration of storage/app/public/uploads, a command with $this->output->progressStart() around ShortPixel\fromFolder($path) is far more practical than dispatching thousands of jobs. Chunk the folder walk your self if it is huge; the SDK will happily try to process everything in one call.

Remote disks require a local round-trip. fromFiles() needs readable local paths. For S3/R2, Storage::disk('s3')->readStream() into storage/app/tmp/, optimize, then push the optimized file back. Use fromUrls() only for genuinely public URLs — it cannot see private objects.

Persist the outcome, not just the file. Add columns like optimized_path, original_bytes, optimized_bytes, optimized_at to your media table. You need the byte counts to prove savings, and optimized_at to make the job idempotent (skip files with a matching hash — re-optimizing burns credits for nothing).

Treat WebP/AVIF as an additional artifact, not a replacement. With 'convertto' => '+webp' the SDK produces a sibling file. Store both paths, serve via <picture> with the original as fallback, and be explicit about which Result accessor returns the converted path in your installed version.

Classify failures instead of blanket-retrying. isSuccess() === false covers everything from "not an image" to "API key invalid" to "quota exceeded". Hard failures should call $this->fail(); transient ones (network, processing) should $this->release(60). Log getMessage() and the status code so you can tell them apart.

Test against an interface, not the SDK. Define ImageOptimizerContract with one optimize() method, bind the real implementation in AppServiceProvider, and swap in a fake in tests. There is no sandbox key, so any integration test costs real credits — keep those manual and use a 10 KB fixture.

Gotchas and Tips

  • Static options leak in long-running workers. Under Octane or a queue worker, ShortPixel::setOptions() persists between jobs. If you ever use multiple keys (per-tenant, per-plan), always set the full option array at the start of each job rather than assuming the previous state.
  • No Laravel integration ships with this package. You will write the config entry, the service binding, and the job yourself. Budget for that; it is 30 minutes of work, not zero.
  • Errors are results, not exceptions. The SDK generally hands back a non-successful Result object rather than throwing. Code that only wraps the call in try/catch will silently produce unoptimized images. Always branch on isSuccess().
  • wait is a polling budget, not a per-request timeout. Set it too low and in-flight images come back as "processing"; set it too high and your job sits idle. For large batches, prefer the async path (ShortPixelClient or the async API mode) plus your own scheduled poller, and keep wait small.
  • Credits differ by compression type — lossless consumes more than lossy/glossy, and conversions may consume additional credits depending on your plan. Check the current pricing table before defaulting a whole application to lossless.
  • Watch file size and format limits. There is a per-image cap and a supported-format list; validate on upload with Laravel's image / mimes rules before burning a queue slot and a credit on a rejection. CMYK JPEGs need 'cmyk2rgb' => 1.
  • getSavedFile() is only meaningful on success. Calling it on a failed result gives you nulls or notices. Guard every accessor with isSuccess().
  • Paths must be absolute and readable. Relative paths and symlinked storage directories are a common source of "file not found" results that look like API failures.
  • Decide overwrite vs. backup deliberately. Overwriting in place is irreversible; either set a backup_path outside the web root or copy the original to storage/app/private/originals/ first. Your S3 versioning does not protect local disk.
  • Pin the version. 36 stars and zero dependents means a small user base and less battle-testing than the major Laravel packages. Commit to a specific ^x.y constraint, read src/Result.php after every upgrade, and keep the wrapper thin so SDK changes are contained to one class.
  • Never expose the key client-side. All optimization goes through queued server-side jobs; the browser should only ever see the resulting URLs.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core