Appearance
Laravel Integration
Generate OG images for Laravel applications.
Setup
1. Configuration
bash
# .env
OG_IMAGE_API_KEY=og_your_api_key_herephp
// config/services.php
return [
// ...
'og_image' => [
'api_key' => env('OG_IMAGE_API_KEY'),
'base_url' => 'https://ogimageapi.io',
],
];2. Create Service Class
php
<?php
// app/Services/OGImageService.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class OGImageService
{
protected string $apiKey;
protected string $baseUrl;
public function __construct()
{
$this->apiKey = config('services.og_image.api_key');
$this->baseUrl = config('services.og_image.base_url');
}
public function generate(array $params): ?string
{
$response = Http::withHeaders([
'X-API-Key' => $this->apiKey,
])->post("{$this->baseUrl}/api/generate", array_merge([
'template' => 'default',
'theme' => 'dark',
], $params));
if ($response->successful()) {
return $response->body();
}
logger()->error('OG Image generation failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
return null;
}
public function generateAndStore(array $params, string $path): ?string
{
$imageData = $this->generate($params);
if (!$imageData) {
return null;
}
Storage::disk('public')->put($path, $imageData);
return Storage::disk('public')->url($path);
}
public function generateForModel($model, string $template = 'blog'): ?string
{
$params = $this->getParamsFromModel($model, $template);
$path = "og-images/{$model->getTable()}/{$model->id}.png";
return $this->generateAndStore($params, $path);
}
protected function getParamsFromModel($model, string $template): array
{
return match($template) {
'blog' => [
'title' => $model->title,
'subtitle' => $model->excerpt ?? '',
'author_name' => $model->author?->name,
'author_avatar_url' => $model->author?->avatar_url,
'template' => 'blog',
],
'product' => [
'title' => $model->name,
'product_image_url' => $model->image_url,
'price' => '$' . number_format($model->price, 2),
'original_price' => $model->compare_price ? '$' . number_format($model->compare_price, 2) : null,
'rating' => $model->average_rating,
'template' => 'product',
],
default => [
'title' => $model->title ?? $model->name,
'subtitle' => $model->description ?? '',
'template' => 'default',
],
};
}
}3. Register Service Provider
php
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use App\Services\OGImageService;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(OGImageService::class);
}
}Usage in Controllers
php
<?php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Services\OGImageService;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function __construct(
protected OGImageService $ogImageService
) {}
public function store(Request $request)
{
$post = Post::create($request->validated());
// Generate OG image
$ogImageUrl = $this->ogImageService->generateForModel($post, 'blog');
if ($ogImageUrl) {
$post->update(['og_image' => $ogImageUrl]);
}
return redirect()->route('posts.show', $post);
}
public function regenerateOG(Post $post)
{
$ogImageUrl = $this->ogImageService->generateForModel($post, 'blog');
if ($ogImageUrl) {
$post->update(['og_image' => $ogImageUrl]);
}
return back()->with('success', 'OG image regenerated');
}
}Model Observer
php
<?php
// app/Observers/PostObserver.php
namespace App\Observers;
use App\Models\Post;
use App\Services\OGImageService;
class PostObserver
{
public function __construct(
protected OGImageService $ogImageService
) {}
public function created(Post $post)
{
$this->generateOGImage($post);
}
public function updated(Post $post)
{
// Regenerate if title or excerpt changed
if ($post->wasChanged(['title', 'excerpt'])) {
$this->generateOGImage($post);
}
}
protected function generateOGImage(Post $post)
{
dispatch(function () use ($post) {
$ogImageUrl = $this->ogImageService->generateForModel($post, 'blog');
if ($ogImageUrl) {
$post->updateQuietly(['og_image' => $ogImageUrl]);
}
})->afterResponse();
}
}Register the observer:
php
// app/Providers/AppServiceProvider.php
public function boot()
{
Post::observe(PostObserver::class);
}Blade Component
php
<?php
// app/View/Components/OGMeta.php
namespace App\View\Components;
use Illuminate\View\Component;
class OGMeta extends Component
{
public function __construct(
public string $title,
public ?string $description = null,
public ?string $image = null,
public string $type = 'website'
) {}
public function render()
{
return view('components.og-meta');
}
}blade
{{-- resources/views/components/og-meta.blade.php --}}
<title>{{ $title }}</title>
<meta name="description" content="{{ $description }}">
<meta property="og:title" content="{{ $title }}">
<meta property="og:description" content="{{ $description }}">
<meta property="og:image" content="{{ $image ?? asset('images/default-og.png') }}">
<meta property="og:type" content="{{ $type }}">
<meta property="og:url" content="{{ url()->current() }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $title }}">
<meta name="twitter:description" content="{{ $description }}">
<meta name="twitter:image" content="{{ $image ?? asset('images/default-og.png') }}">Usage:
blade
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('head')
<x-og-meta
:title="$post->title"
:description="$post->excerpt"
:image="$post->og_image"
type="article"
/>
@endsection
@section('content')
<article>
<h1>{{ $post->title }}</h1>
{!! $post->content !!}
</article>
@endsectionAPI Route for Dynamic Images
php
<?php
// routes/api.php
use App\Services\OGImageService;
use Illuminate\Http\Request;
Route::get('/og', function (Request $request, OGImageService $ogImage) {
$params = $request->validate([
'title' => 'required|string|max:200',
'subtitle' => 'nullable|string|max:300',
'template' => 'nullable|string',
'theme' => 'nullable|in:dark,light',
]);
$imageData = $ogImage->generate($params);
if (!$imageData) {
abort(500, 'Failed to generate image');
}
return response($imageData)
->header('Content-Type', 'image/png')
->header('Cache-Control', 'public, max-age=86400');
});Artisan Command
php
<?php
// app/Console/Commands/GenerateOGImages.php
namespace App\Console\Commands;
use App\Models\Post;
use App\Services\OGImageService;
use Illuminate\Console\Command;
class GenerateOGImages extends Command
{
protected $signature = 'og:generate {--model=post} {--id=}';
protected $description = 'Generate OG images for models';
public function handle(OGImageService $ogImage)
{
$modelClass = match($this->option('model')) {
'post' => Post::class,
// Add other models...
default => throw new \InvalidArgumentException('Unknown model'),
};
$query = $modelClass::query();
if ($id = $this->option('id')) {
$query->where('id', $id);
}
$models = $query->get();
$bar = $this->output->createProgressBar($models->count());
foreach ($models as $model) {
$url = $ogImage->generateForModel($model, $this->option('model'));
if ($url) {
$model->updateQuietly(['og_image' => $url]);
}
$bar->advance();
}
$bar->finish();
$this->newLine();
$this->info('Done!');
}
}Usage:
bash
php artisan og:generate --model=post
php artisan og:generate --model=post --id=123Best Practices
- Queue image generation — Don't block requests
- Use observers — Automatic regeneration
- Store locally — Faster than API every time
- Create commands — Batch operations
- Cache responses — 24h cache headers