
Media management is a critical part of any modern web application. Instead of storing and serving large image and video files directly from your server, you can offload this to a powerful media service like Cloudinary. It not only provides storage, but also offers real-time transformations, optimization, and global delivery through CDN.
In this tutorial, we’ll walk through integrating Cloudinary with a Laravel application to upload and manage images seamlessly.
1. Create a Free Cloudinary Account
- Go to Cloudinary Signup.
- Create a free account using your email, Google, or GitHub login.
- After logging in, navigate to the Dashboard → you’ll see your Cloud name, API Key, and API Secret. These are needed for integration.
2. Create an Upload Preset
Cloudinary uses upload presets to define upload rules (like allowed formats, storage folder, transformations).
- In your Cloudinary Dashboard, go to Settings → Upload → Upload Presets.
- Click Add Upload Preset.
- Give it a name (e.g., laravel_preset) and configure options as needed.
- Save it.
3. Create a Fresh Laravel Project
If you don’t already have a Laravel project, create one:
composer create-project laravel/laravel cloudinary_demo
4. Install the Cloudinary SDK
Install the Cloudinary Laravel SDK:
composer require cloudinary-labs/cloudinary-laravel
Then publish the configuration file:
php artisan vendor:publish --tag=cloudinary
5. Configure .env
In your Laravel project root, open the .env file and add the Cloudinary credentials.
#Cloudinary Configuration
CLOUDINARY_URL=cloudinary://<API_KEY>:<API_SECRET> @<CLOUD_NAME>
CLOUDINARY_UPLOAD_PRESET=laravel_preset
6. Create Model and Migration
We’ll store uploaded photo details in a database. Run:
php artisan make:model Photo -m
Update the migration file (database/migrations/xxxx_create_photos_table.php):
Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->string('title')->nullable();
$table->string('image_url');
$table->string('image_public_id');
$table->timestamps();
});
Run the migration:
php artisan migrate
7. Create a Controller
Generate a controller:
php artisan make:controller PhotoController
Update app/Http/Controllers/PhotoController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Photo;
use CloudinaryLabs\CloudinaryLaravel\Facades\Cloudinary;
class PhotoController extends Controller
{
public function index()
{
$photos = Photo::latest()->get();
return view('photos.index', compact('photos'));
}
public function create()
{
return view('photos.create');
}
public function store(Request $request)
{
$request->validate([
'title' => 'nullable|string|max:255',
'image' => 'required|image|max:2048',
]);
$file = $request->file('image');
// Upload to Cloudinary
$uploadResult = Cloudinary::upload($file->getRealPath(), [
'folder' => 'laravel_uploads',
'upload_preset' => env('CLOUDINARY_UPLOAD_PRESET'),
]);
Photo::create([
'title' => $request->title,
'image_url' => $uploadResult->getSecurePath(),
'image_public_id' => $uploadResult->getPublicId(),
]);
return redirect()->route('photos.index')->with('success', 'Photo uploaded successfully!');
}
public function destroy(Photo $photo)
{
Cloudinary::destroy($photo->image_public_id);
$photo->delete();
return redirect()->route('photos.index')->with('success', 'Photo deleted successfully!');
}
}
8. Define Routes
In routes/web.php:
use App\Http\Controllers\PhotoController;
Route::get('/photos', [PhotoController::class, 'index'])->name('photos.index');
Route::get('/photos/create', [PhotoController::class, 'create'])->name('photos.create');
Route::post('/photos', [PhotoController::class, 'store'])->name('photos.store');
Route::delete('/photos/{photo}', [PhotoController::class, 'destroy'])->name('photos.destroy');
9. Create Blade Views
Resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Laravel Cloudinary Demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
@yield('content')
</div>
</body>
</html>
resources/views/photos/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<h2>Uploaded Photos</h2>
<a href="{{ route('photos.create') }}" class="btn btn-primary mb-3">Upload New Photo</a>
<div class="row">
@forelse($photos as $photo)
<div class="col-md-4 mb-3">
<div class="card">
<img src="{{ $photo->image_url }}" class="card-img-top" alt="Photo">
<div class="card-body">
<h5>{{ $photo->title ?? 'Untitled' }}</h5>
<form action="{{ route('photos.destroy', $photo) }}" method="POST">
@csrf
@method('DELETE')
<button class="btn btn-danger w-100">Delete</button>
</form>
</div>
</div>
</div>
@empty
<p>No photos uploaded yet.</p>
@endforelse
</div>
</div>
@endsection
Resources/views/photos/create.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Upload Photo</h1>
<form method="POST" action="{{ route('photos.store') }}" enctype="multipart/form-data">
@csrf
<div class="mb-3">
<label for="title">Title</label>
<input type="text" name="title" class="form-control">
</div>
<div class="mb-3">
<label for="image">Choose Image</label>
<input type="file" name="image" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
</div>
@endsection
10. Test the Application
- Run the app:
php artisan serve
- Visit http://<host:port>/photos/create.
- Upload an image → it will be stored in Cloudinary and a record saved in your database.
Conclusion
You’ve successfully integrated Cloudinary with Laravel. Now your images are stored in the cloud, optimized automatically, and served globally via CDN. From here, you can explore advanced features like transformations (resize, crop, format conversion), video uploads, and automatic optimization.