winter/wn-tinypng-plugin
Winter CMS plugin that integrates TinyPNG to automatically compress and optimize PNG and JPEG images, reducing file sizes while preserving quality. Easy to configure and works with the CMS media manager and uploads.
composer require winter/wn-tinypng-plugin
php artisan winter:up
.env rather than committing it:
TINYPNG_API_KEY=xxxxxxxxxxxxxxxx
config/config.php or the plugin's Plugin.php for the expected config key.plugins/winter/tinypng/Plugin.php (event listeners and Twig registrations), models/ (settings model and any stored-file relation), classes/ (the compressor/client wrapper), and the README for the exact namespace.System\Models\File creation, upload a test JPEG in the Media manager, and confirm the stored file size drops and the image still renders.Names in the examples below are illustrative — verify the actual namespace and method signatures in Plugin.php before copying.
Compress on upload via model events. The most common day-to-day pattern is listening to the attachment lifecycle rather than asking editors to compress manually:
// Plugin.php boot()
Event::listen('eloquent.created: System\Models\File', function ($file) {
if (in_array($file->getExtension(), ['jpg', 'jpeg', 'png', 'webp'])) {
\Winter\TinyPNG\Classes\Compressor::compress($file->getLocalPath());
}
});
For custom models with attached files, prefer a trait or a File relation observer so the behaviour is explicit and testable.
Wrap it in a service/queue job. Never call the API inline for multi-image uploads. Dispatch a job per file so a slow or rate-limited response doesn't block the admin request:
Queue::push(\App\Jobs\CompressImage::class, ['path' => $file->getLocalPath()]);
Backend integration points. Useful places to expose it:
php artisan tinypng:compress-existing) for a one-off backfill of the media library.Twig/frontend helpers. If the plugin registers a Twig filter or function, use it for on-demand resizing:
<img src="{{ image.path|tinypng_resize(800) }}" alt="{{ image.title }}">
If no Twig helper exists, expose your own via Plugin::registerMarkupTags() that delegates to the plugin's compressor class.
Configuration over code. Keep the API key and default options (preserve metadata, resize presets, overwrite vs. sibling file) in config so staging and production can use different TinyPNG accounts and quotas:
// config/config.php
return [
'api_key' => env('TINYPNG_API_KEY'),
'overwrite' => true,
'preserve_metadata' => false,
];
Resize/convert as part of the pipeline. TinyPNG also does resizing and format conversion; if the plugin exposes those, chain them in one job (compress + resize to a max width) rather than making two API round-trips.
Compression-Count response header and fail gracefully on 429. A single large upload loop can exhaust the monthly quota in minutes.tinypng_compressed_at flag or hash so re-saving a model doesn't burn a compression credit on an unchanged file. TinyPNG will re-compress happily and bill you for it.curl -u api:YOUR_KEY https://api.tinypng.com/shrink -d @test.jpg. A 401 means key/encoding, not plugin code..env, run php artisan config:clear — Winter caches config and the old key will persist.vendor/ (or the Packagist source link) to confirm you're installing what you think you are.file_get_contents-style HTTP calls, wrap it in your own adapter so you can swap in a mocked client for tests and a different CDN-backed compressor later.How can I help you explore Laravel packages today?