Generating Open Graph images for each post in Hyde

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. Dynamic open graph image exmaple

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.

📁
app/Providers/AppServiceProvider.php
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(): void
11 {
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.

Replace the default values below to match your website.
📁
resources/views/vendor/hyde/layouts/head.blade.php
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
Make sure to add a default 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.

📁
resources/views/vendor/hyde/layouts/post.blade.php
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
Remove any duplicative meta tags from 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.

📁
app/Commands/MakeOpengraphImage.php
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 Command
15{
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(): int
21 {
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(): MarkdownPost
40 {
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): void
52 {
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.

🤖
Did you spot a mistake in this article? Have a suggestion for how something can be improved? Even if you'd just like to comment or chat about something else, I'd love to hear from you! Contact me.

Syntax highlighting by Torchlight.dev

End of article