Overview
This article demonstrates how to generate a unique Open Graph image for each blog post in HydePHP. We will configure meta tags to use post-specific images when available, falling back to a default image otherwise. We will also create an artisan command that generates images using the Intervention Image library.
Below is a sample Open Graph image used for this article.

Use case
For when you want to generate an opengraph image for each of your posts and include the appropriate meta tags automatically.
Assumptions
You have a working HydePHP 2 project with Blade views published.
HTML meta tags
Open Graph meta tags control how your content appears when shared on social platforms like Facebook, LinkedIn, and Slack. Twitter has its own set of meta tags (prefixed with twitter:) that define how links appear in tweets. Together, these tags let you specify a title, description, and image that display when someone shares your URL.
Blade directive
To conditionally render default meta tags only when a page does not push its own, we need a way to check if a Blade stack has content. Laravel does not include this functionality out of the box, so we will create a custom @hasStack directive.
1<?php 2 3namespace App\Providers; 4 5use Illuminate\Support\Facades\Blade; 6use Illuminate\Support\ServiceProvider; 7 8class AppServiceProvider extends ServiceProvider 9{10 public function register(): void11 {12 //13 }14 15 public function boot(): void 16 {17 Blade::if('hasStack', function($stackName) {18 return !empty(view()->yieldPushContent($stackName));19 });20 }21}
Opengraph stack
In the head layout, we use our new @hasStack directive to check if the current page has pushed content to the opengraph stack. If it has, we render that content. Otherwise, we render default meta tags for the site.
1{!! config('hyde.head') !!} 2{!! Includes::html('head') !!} 3 4@hasStack('opengraph') 5 @stack('opengraph') 6@else 7 {!! Meta::property('og:url', config('hyde.url')) !!} 8 {!! Meta::property('og:type', 'website') !!} 9 {!! Meta::property('og:title', config('hyde.name')) !!}10 {!! Meta::property('og:description', 'Hi, I\'m Brice. I like to code.') !!}11 {!! Meta::property('og:image', config('hyde.url') . '/media/opengraph.png') !!}12 {!! Meta::name('twitter:card', 'summary_large_image') !!}13 {!! Meta::name('twitter:domain', 'brice.codes') !!}14 {!! Meta::name('twitter:url', config('hyde.url')) !!}15 {!! Meta::name('twitter:title', config('hyde.name')) !!}16 {!! Meta::name('twitter:description', 'Hi, I\'m Brice. I like to code.') !!}17 {!! Meta::name('twitter:image', config('hyde.url') . '/media/opengraph.png') !!}18@endif
opengraph.png file to your _media directory.
Meta tags
In the post layout, we push content to the opengraph stack with post-specific meta tags. The image path checks if a post-specific Open Graph image exists; if not, it falls back to the default image.
1@php($img = config('hyde.url') . '/media/' . (\Illuminate\Support\Facades\File::exists(base_path("_media/$page->identifier-opengraph.png")) ? "$page->identifier-opengraph.png" : 'opengraph.png')) 2@php($url = config('hyde.url') . '/posts/' . $page->identifier . '.html') 3 4@push('opengraph') 5 {!! Meta::property('og:url', $url) !!} 6 {!! Meta::property('og:type', 'website') !!} 7 {!! Meta::property('og:title', $page->title) !!} 8 {!! Meta::property('og:description', $page->description) !!} 9 {!! Meta::property('og:image', $img) !!}10 {!! Meta::name('twitter:card', 'summary_large_image') !!}11 {!! Meta::name('twitter:domain', 'brice.codes') !!}12 {!! Meta::name('twitter:url', $url) !!}13 {!! Meta::name('twitter:title', $page->title) !!}14 {!! Meta::name('twitter:description', $page->description) !!}15 {!! Meta::name('twitter:image', $img) !!}16@endpush
config/hyde.php in the head section to avoid conflicts.
Generation command
Since HydePHP is built on Laravel Zero, we can create custom artisan commands. We will create a command that reads the base Open Graph image, overlays the post title and description as text, and saves the result.
Installing Intervention Image
We will use the Intervention Image library to manipulate images. Install it with Composer:
1composer require intervention/image
Downloading fonts
The image generation command uses font files for text overlays. Download a font from Google Fonts and place the TTF files in resources/fonts/. This article uses Roboto, but you can substitute any font by updating the paths in the command.
Command implementation
Create the following command to generate Open Graph images. The command supports generating an image for a single post (interactive selection) or for all posts at once.
1<?php 2 3namespace App\Commands; 4 5use Hyde\Pages\MarkdownPost; 6use Illuminate\Support\Facades\File; 7use Intervention\Image\Drivers\Gd\Driver; 8use Intervention\Image\ImageManager; 9use LaravelZero\Framework\Commands\Command;10use SplFileInfo;11 12use function Laravel\Prompts\select;13 14class MakeOpengraphImage extends Command15{16 protected $signature = 'make:og-img {--all : Generate images for all posts}';17 18 protected $description = 'Generate an opengraph image for a post.';19 20 public function handle(): int21 {22 if ($this->option('all')) {23 $posts = collect(File::files(base_path('_posts')))24 ->map(fn (SplFileInfo $file) => MarkdownPost::parse(str_replace('.md', '', $file->getFilename())));25 26 foreach ($posts as $post) {27 $this->generateImage($post);28 }29 } else {30 $post = $this->promptForMarkdownPost();31 $this->generateImage($post);32 }33 34 $this->info('Done.');35 36 return static::SUCCESS;37 }38 39 protected function promptForMarkdownPost(): MarkdownPost40 {41 $postFileName = select(42 label: 'Which post?',43 options: collect(File::files(base_path('_posts')))44 ->map(fn (SplFileInfo $file) => $file->getFilename())45 ->toArray()46 );47 48 return MarkdownPost::parse(str_replace('.md', '', $postFileName));49 }50 51 protected function generateImage(MarkdownPost $post): void52 {53 $this->line("Generating image for post: $post->title...");54 55 $baseImagePath = base_path('_media/opengraph.png');56 $finalImagePath = base_path('_media/' . $post->identifier . '-opengraph.png');57 58 $manager = new ImageManager(new Driver());59 $image = $manager->read($baseImagePath);60 61 $image->text($post->title, 136, 434, function ($font) {62 $font->file(base_path('resources/fonts/Roboto-Bold.ttf'));63 $font->size(36);64 $font->color('#364153');65 });66 67 $image->text($post->description ?? '', 136, 470, function ($font) {68 $font->file(base_path('resources/fonts/Roboto-Regular.ttf'));69 $font->size(24);70 $font->color('#364153');71 $font->wrap(928);72 $font->lineHeight(1.6);73 $font->valign('top');74 });75 76 $image->save($finalImagePath);77 }78}
Handle method
The handle method checks for the --all flag. If present, it collects all markdown posts from the _posts directory and generates an image for each. Otherwise, it prompts the user to select a single post.
Prompt for markdown post method
The promptForMarkdownPost method uses Laravel Prompts to display an interactive selection menu. It lists all files in the _posts directory and returns a parsed MarkdownPost instance for the selected file.
Generate image method
The generateImage method reads the base Open Graph image, adds the post title and description as text overlays at specific coordinates, and saves the result. The font files, sizes, and positions can be adjusted to match your design.
For a single post
Run the command without any flags to interactively select a post:
1php hyde make:og-img
For all posts
Use the --all flag to generate images for every post in your _posts directory:
1php hyde make:og-img --all
Conclusion
We configured HydePHP to use dynamic Open Graph images for blog posts. The custom @hasStack directive enables conditional rendering of default meta tags, while post-specific layouts push their own meta content. The artisan command generates images by overlaying post titles and descriptions onto a base template using Intervention Image.