Watermarking Images using Laravel & Cloudinary

Banner for a MediaJam post

Eugene Musebe

Laravel is a PHP framework developed with developer productivity in mind. The framework also aims to evolve with the web and has already incorporated several new features and ideas in the web development world—such as job queues, API authentication out of the box, real-time communication, and much more.


In this article, we’ll explore the ways you can build a simple Cloudinary Watermark API using Laravel. The API will allow you to add a watermark to an image using Cloudinary's powerful transformational API. We’ll be using Laravel 8, and all of the code is available for reference on GitHub.


You can find the full source code on my Github repository.

PHPSandbox and Github

The final project can be viewed on PHPSandbox


Using Cloudinary in your Laravel projects is pretty straightforward. However, for you to be able to easily follow along, you need to have a good command of your terminal, Git, and entry knowledge of PHP specifically with the Laravel framework.

You also need to understand what a RESTful API is. REST stands for REpresentational State Transfer and is an architectural style for network communication between applications, which relies on a stateless protocol (usually HTTP) for interaction.

Lastly, you will need an API Client/Platform such as Postman or Insomnia installed on your system to test the API endpoints.

Getting Started

Being that Laravel is a PHP Framework, we will need Composer. Like any modern PHP framework, Laravel uses Composer to manage its dependencies. So, before we can start ensure you have Composer installed on your machine. Follow step 1 below to install Composer and PHP.

  1. Install Composer and PHP on your development or production machine.

  2. Install Laravel

    1. Via Composer:

      composer create-project --prefer-dist laravel/laravel cloudinary-watermark-api

    2. Via Laravel Installer

      composer global require laravel/installer

      laravel new cloudinary-watermark-api

  3. In step 2 above we have installed the Laravel Installer and used it to scaffold a new application in the folder cloudinary-watermark-api. With Laravel installed, we should be able to start and test the server ensuring everything is okay. Change the directory to the project folder and run the local development server by typing the following commands:

    cd cloudinary-watermark-api

    php artisan serve

The Laravel project is now up and running. When you open http://localhost:8000 on your computer, you should see the image below:

Laravel Server Running

Routes and Controllers

In the routes/ folder you will notice that Laravel has a couple of files. We will work with the routes/api.php file to create the routes we need for our API.

For a start, we will need two endpoints, one to upload the watermark to Cloudinary and the other to create the watermark branded images, but you can add more routes you need as you continue exploring the Cloudinary APIs.

We have a route now we need to create a controller. Run the following commands to create one.

php artisan make:controller WatermarkController

This will create the controller app/Http/Controllers/WatermarkController.php


At the moment it is empty, but we will populate it shortly. First things first, let us set up Cloudinary 🙌🏾😊.

Setting up Cloudinary’s Laravel SDK

Cloudinary has a tonne of features from media upload, storage, administration, and manipulation to optimization and delivery. In this case, we will use Cloudinary's transformation features to create an API endpoint that will automatically overlay a brand/watermark image over our media.

That way our media content will be authentic and super professional which is a requirement for excellent brand management

To get started:

  1. Sign up for a free Cloudinary account then navigate to the Console page and take note of your Cloud name, API Key and API Secret.

    Cloudinary Dashboard

  2. Install Cloudinary’s Laravel SDK:

    composer require cloudinary-labs/cloudinary-laravel

Note: Please ensure you follow all the steps in the #Installation section. Publish the configuration file and add the Cloudinary credentials you noted in Step 1 to the .env file.

Creating the Watermark Endpoint

We will make use of the WatermarkController we had created earlier. It will take two required inputs from the POST request:

  1. watermark - This is a required image input of type PNG since we need our watermark to be transparent in order to overlay it over other images.
  2. public_id - We need to implicitly provide the public id for our watermark since we will need to specify this when performing the overlay transformation later on.

Open the file app/Http/WatermarkController.php and add the following code:

  1. First we create the function upload which will correspond to our watermark/upload endpoint.
1public function upload(Request $request) {
  1. Add the functionality to validate the required inputs, upload the watermark to Cloudinary and send a response to the user. Here is how it works:
1public function upload(Request $request): JsonResource {
2 // Validates the request from the user
3 // If the validation failed, throw a ValidationException and inform the user.
4 $data = $this->validate($request, [
5 'watermark' => [
6 'required',
7 'image',
8 'mimes:png',
9 ],
10 'public_id' => [
11 'required',
12 'string'
13 ]
14 ]);
16 // If there are no validation errors, proceed and upload the watermark to Cloudinary and return a Json response to the user.
17 $watermark = $data['watermark'];
18 $public_id = $data['public_id'];
19 cloudinary()->upload($watermark->getRealPath(), [
20 'folder' => 'watermark-api',
21 'public_id' => $public_id
22 ])->getSecurePath();
24 return JsonResource::make([
25 'message' => "Watermark created successfully",
26 'watermark' => ['public_id' => $public_id]
27 ]);
  1. Now we can link this in the routes file. Open your routes/api.php and add the following code:

Route::post('watermark/upload', [WatermarkController::class, 'upload']);

Testing: You can test the route using Postman or Insomnia. Below is an example of a successful and failed response:

Successful API Response
Successful API Response
Failed API Response
Failed API Response

Awesome, moving on nicely.

Creating the Overlay or Branding Endpoint

By now you know how to create an endpoint, this should be a breeze. We will use the same WatermarkController but create a new method for this endpoint.

Similar to the previous endpoint this one will also take two inputs:

  1. media - This is a required image/video input. It is the media file that we want to be branded with our cool watermark, which we created earlier.
  2. public_id - This is the public id of the watermark we passed to the previous endpoint. This needs to be exactly the same as the one we provided previously.

In our WatermarkController add the following method:

1public function create(Request $request): JsonResource {

We will populate the create method above with the following code:

1public function create(Request $request): JsonResource {
2 // Validation
3 $data = $this->validate($request, [
4 'media' => ['required', 'image', 'max:1024'],
5 'public_id' => [
6 'required',
7 'string'
8 ]
9 ]);
11 // Uploading image to Cloudinary with transformation parameters that will overlay our watermark on our image
12 $media = $data['media'];
13 $public_id = $data['public_id'];
14 $branded = cloudinary()->upload($media->getRealPath(), [
15 'folder' => 'watermark-api',
16 'transformation' => [
17 'overlay' => $public_id,
18 'gravity' => 'south_east', // watermark location bottom right
19 'x' => 0.02, // 2 percent offset horizontally
20 'y' => 0.02, // 2 percent offset vertically
21 'crop' => 'scale',
22 ],
23 ])->getSecurePath();
25 // We return a response to the user with the URL of the branded or watermarked image
26 return JsonResource::make([
27 'message' => "Watermark created successfully",
28 'url' => $branded
29 ]);

Important: Take note of the following transformation that makes the magic happen. Please refer to this Cloudinary Layers documentation to learn more about these transformation options and experiment with several other awesome features:

  1. overlay - This is the public id of the watermark we uploaded earlier. Please note that since we provided a folder_name when uploading the watermark. We will need to include the folder name in the public_id input we send to the server, otherwise, Cloudinary will look for the watermark in the root folder and not find it.

  2. gravity - This is where the overlay will be placed. it can be center, north, south, east, and so on and so forth.

  3. x and y offsets - These parameters accept either integer values representing the number of pixels to adjust the overlay position in the horizontal or vertical directions or decimal values representing a percentage-based offset relative to the containing image (e.g., 0.2 for an offset of 20%).

  4. Install Livewire Package by running the following command in your Laravel project: composer require livewire/livewire

If you followed along with this article keenly, you should be able to use an API client such as Postman or Insomnia to hit the endpoints to upload a watermark or create a watermarked/branded image similar to the one below:

Note: Cloudinary is super powerful for the management of your media assets in your project that will not only optimize your assets for visual quality but also cost savings in terms of performance, storage, and AI-powered transformations as well.

Excel with Cloudinary

Building an API is a whole technical process that requires you to think about the goals, architecture, testing, scaling, security, and more, but in this implementation, with Cloudinary we had fun building a very simple API.

Cloudinary is your A to Z media management solution - upload, storage, administration, manipulation, optimization, and delivery.

Get started with Cloudinary in your Laravel projects for FREE!

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.