Caching External Images into Laravel Site

I discovered an inventive approach to have my Laravel server automatically save images for me. It’s quite useful, and I’m thrilled to show you how to use it. Let’s escape the deep code sea and instead take some simple measures together.


I’ve been tinkering around with my website lately and stumbled upon a little ethical dilemma. You see, I’ve been hotlinking images from external sources. It’s easy, sure, but then it dawned on me: I’m using their resources. That’s when my conscience nudged me, and I thought, « Why not be a good netizen and host those images myself? »

So, I and figured out a way to cache these images on my Laravel-powered server automatically. It’s pretty nifty, and I’d love to share it with you. Let’s dive into the how-to without getting our feet wet with complex coding oceans!

First things first, I set up a route in my web.php file:

Route::get('/assets/images/{basename}', 'ImageCacheController@getImage')->where('basename', '.*');

This little snippet tells Laravel to send requests for images in my /assets/img/ directory to a controller that can handle them smartly.

Next, I created my ImageCacheController. This controller is the heart of the operation. It checks if the requested image is lounging on my server. If not, it fetches the image from the original URL and saves it to my server. Here’s the magic in PHP form:

// ImageCacheController.php

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class ImageCacheController extends Controller
    public function getImage($basename)
        // Decode the base64 string and determine the file extension
        $encodedUrl = Str::beforeLast($basename, '.');
        $extension = Str::afterLast($basename, '.');
        $imageUrl = base64_decode(str_replace(['-', '_'], ['+', '/'], $encodedUrl));

        // Check if the image already exists in local storage
        $localPath = 'assets/img/' . $basename;
        if (Storage::disk('public')->exists($localPath)) {
            return response()->file(Storage::disk('public')->path($localPath));

        // If the image doesn't exist locally, fetch and store it
        try {
            $imageContent = file_get_contents($imageUrl);
            Storage::disk('public')->put($localPath, $imageContent);
            return response($imageContent)->header('Content-Type', 'image/' . $extension);
        } catch (\Exception $e) {

In this controller, we’re handling different image formats and making sure that any encoded slashes don’t accidentally create subdirectories. We’re being respectful not only to the original image hosts but also to our own file system.

Once we have the server-side sorted out, let’s not forget the view! Integrating this into our Laravel Blade template is a piece of cake. We just need a tiny helper function to make our URLs tidy and server-friendly. Here’s a little helper I wrote in my helpers.php file:

 * Create a cached image URL based on a given external URL.
 * @param  string $url
 * @return string
function cachedImg($url)
    $base64 = base64_encode($url);
    $base64 = str_replace(['+', '/'], ['-', '_'], $base64); // Making it file-system friendly

    return '/assets/img/' . $base64 . '.png'; // We're defaulting to .png for simplicity

All this function is doing is encoding our external image URL into a base64 string that’s safe for our server’s file paths. Then, we simply use it in our Blade templates like so:

    $logo = '';
<img src="{{ cachedImg($logo) }}" alt="" class="w-12 h-12">

And voilà! With this, the <img> tag uses the helper to generate a URL that routes through our caching controller. The first time someone requests this image, it’ll be fetched and stored locally, and from then on, it’s served up from our own server.

By adding these final touches, our images are seamlessly integrated into our site’s templates, and they’re served up quickly and efficiently. And, of course, we’re being good digital citizens by not leeching bandwidth from others.