Tinypng compressor plugin

About Tinypng

One of the best services for compressing images is https://tinypng.com/. Each month you can compress up to 500 images free. If you need to compress more images each month it costs, but it’s at a low price ($0.009 per image).

Compressing images is good for loading time, better user experience and SEO.

Tinypng compressor plugin - For Kirby

A good thing is that there is an Tinypng API. Another great things is that Kirby now support hooks!


  • Save it as /tinypng/tinypng.php in your plugin folder.
  • Download http://curl.haxx.se/ca/cacert.pem and put it in the same folder.
  • If you DON’T want to overwrite the same file, change the last $input to $output instead.

Here is the code…

kirby()->hook('panel.file.upload', function( $page ) {
	$path = kirby()->roots()->content();

	$key = "YOUR API KEY";
	$input = $page->dir() . '/' . $page->filename();
	$output = $page->dir() . '/' . $page->name() . '-min' . '.' . $page->extension();

	tinypng_image_compressor( $key, $input, $input );

function tinypng_image_compressor( $key, $input, $output ) {
	$request = curl_init();
	curl_setopt_array($request, array(
		CURLOPT_URL => "https://api.tinify.com/shrink",
		CURLOPT_USERPWD => "api:" . $key,
		CURLOPT_POSTFIELDS => file_get_contents($input),
		CURLOPT_CAINFO => __DIR__ . "/cacert.pem",

	$response = curl_exec($request);
	if (curl_getinfo($request, CURLINFO_HTTP_CODE) === 201) {
		$headers = substr($response, 0, curl_getinfo($request, CURLINFO_HEADER_SIZE));
		foreach (explode("\r\n", $headers) as $header) {
			if (strtolower(substr($header, 0, 10)) === "location: ") {
				$request = curl_init();
				curl_setopt_array($request, array(
					CURLOPT_URL => substr($header, 10),
					CURLOPT_CAINFO => __DIR__ . "/cacert.pem",
				file_put_contents($output, curl_exec($request));
	} else {
		print("Compression failed");



This is really awesome. Is there a way to run it automatically with everything in the thumbs folder?

I’m not sure if it will help on the thumbnail folder.
I just checked with a 11.1 KB JPEG image (200x200), and TinyJPG saved exactly 0 bytes…

However, I did write a Perl script that uses the API to convert images, and I can whip up something that will process all images in a folder if you want…

I will probably write one after my vacation in about 3 weeks.

@jenstornell @JAVE That’d be awesome.

Also, I’m envious of your 3 week vacation!

Here you go.
This script will convert all .jpg & .png files in the given directory.
If you run it with ‘overwrite’ as second parameter, it will do just that :smile:

It’s a bit sparse as far as feedback goes: If all goes will it will be silent for a while, and then return the prompt.

# Tiny PNG/Tiny JPG API interface
use strict;
use LWP;

use constant API_KEY => '<INSERT API KEY HERE>';
use constant API_URL => 'https://api.tinify.com/shrink';


sub main {
    my $input     = $ARGV[0];
    my $overwrite = $ARGV[1];

    my $save_prefix = 'tiny_';
    if ($overwrite eq 'overwrite') {
        $save_prefix = '';

    unless ($input) {
        die "\nUsage: $0 <input dir> ['overwrite']\nIf 'overwrite' is added, the source files will be overwritten\n\n";

    unless(-d $input) {
        die "\nDirectory $input not found!\n\n";

    my @files = read_dir($input);

    foreach my $file (@files) {
        my $data = slurp_file($input . '/' . $file);

        my $agent = new LWP::UserAgent;

        my $request = new HTTP::Request(POST => API_URL);

        my $response = $agent->request($request);

        if ($response->code() eq '201') {
            my $result_url = $response->header('Location');

            $request = new HTTP::Request(GET => $result_url);
            $response = $agent->request($request);

            my $result_data = $response->content();

            my $output = $input . '/' . $save_prefix . $file;
            store_data(file => $output, data => $result_data);
        } else {
            print "Error " . $response->code() . " while trying to shrink $input\n";

sub read_dir {
    my $dir = shift;

    opendir(DIR,$dir) or die "Could not open dir $dir! ($!)\n";
    my @files = grep { /\.(jpg|png)$/} readdir(DIR);

    return @files;

sub slurp_file {
    my $file = shift;
    my $str   = "";
    local *F;
    if (!open (F,"<$file")) {
    } else {
        local $/ = undef;
        $str = <F>;

sub store_data {
    my %param  = @_;
    my $data   = $param{'data'};
    my $output = $param{'file'};

    open(OUTFILE,">$output") || die "\nCould not open $output ($@)\n\n";
    print OUTFILE $data;
1 Like

Thanks, it works great! I had tried doing this (the hard way) a while ago with only partial success :joy:

I have made a Kirby plugin that are compressing thumbnails.

The only problem left is naming the function that will replace the thumb() function.

Some suggestions

  • thumbC()
  • nail()
  • tinypng()
  • tinythumb()

Please help me choose a function name! Give me own suggestions if you have any.

Some notes about the plugin

  • It uses the exact same arguments as the thumb() function.
  • It will only try to compress the thumb with tinypng if the compressed image does not already exists.
  • It will return the compressed image if it exist, else it will return the original thumbnail. That way it will always return an image.
  • No txt-files are added, just compressed thumbnails.
  • It will respect thumbs.filename and thumbs root folder path.
  • I need to test it more before release.
  • The release will be on github, free for everyone.


echo thumbCompressFunction( $page->image(), array( 'width' => 300 ) );

…or get the data…

$thumb = thumbCompressFunction( $page->image(), array( 'width' => 300 ) );

echo $thumb->width();
echo $thumb->height();
echo $thumb->dir();

…or get the original data (scroll example right)…

$thumb = thumbCompressFunction( $page->image(), array( 'width' => 300 ) )->original();

echo $thumb->width();
echo $thumb->height();
echo $thumb->dir();


If you are adventurous you can try out this gist until I release the plugin for real. Read comment for basic instructions.

As said, the function / file name is not yet set.

I tried the PHP version and works perfectly. Great job!!
I have one question, I’m not an expert, what if I would like to keep the metadata?

So it still works? I left the project because I no longer think it’s a good idea to let an external service compress the images.

I don’t know about how to keep the metadata. My guess is that one reason the files are so small in the end is that it skip the metadata.