<<< Back to the blog

Generate dynamic social share images in Craft CMS

it me
Published on
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

Last year I had an interesting challenge of needing to have Craft generate a dynamic social share image per entry without the content editor needing to upload one.

While I was at Dawson Andrews last year, I took the lead on building out a job board focused on the sports industry and we launched at the end of November.

The premise of SportsWork is simple: clubs or organisations pay a fee to list their jobs on the platform and job seekers can search the listings using the power search tool in lightning quick time.

Each listing is unique, so using a standard image when a listing is shared on social media didn't make sense. Also, a club or organisation wouldn't likely have the in-house skills to produce a custom image for their listing.

After a little bit of thought, I had the idea to use headless Chrome and the content from the job listing in Craft to create the social image.

The first step in the process was to create a URL that would serve up what the image would look like.

Next up, the associated Twig template

There's nothing particularly special going on here. It looks up the entry, sets a logo to use, defines a thumbnail logo size and then renders some HTML that I then use Tailwind CSS to style...building a template the way you'd like it to look. That's the easy part. The next part involves extending Craft's default capabilities. Effectively writing our own custom code.

When you create a new Craft project, you get your own Module out of the box, you just need to update config/app.php to enable it, by removing the comment on line 27 to give us:

With the module initialised, we can make the magic happen in Module.php

I want my images to be generated any time a user edits a listing in Craft that isn't a revision, isn't a draft but has a status of live.

Inside the init function of Module.php, I can add

Effectively after an element is saved it

  • check the element is an entry

  • and the entry is in the the userPostedJobs section in Craft

  • and the entry is not a revision

  • and the entry is not draft

  • and the entry is live

  • push a job GenerateSocialCard to the Craft queue, passing the id of the entry

The reason for passing the generation of the social card to the queue is that our process will take time and we don't want to have our user wait before moving onto their next task, so it happens in the background.

Here's the job that gets triggered

As you can see, there's not much to this class. It is added to a jobs directory inside our modules directory. Everything happens in the execute function.

We use the excellent Browsershot PHP package from Spatie to

  1. look at the URL we defined

  2. pass the path to our Node and Npm binaries from our environment file

  3. set the window size to match our CSS (based on the best size for a social image)

  4. wait until the page has loaded in headless Chrome

  5. save the generated image into a directory

That will generate the image on the server for our listing to use when the listing is share on social media.

The last things that we need to do is update our master Twig layout template and the template for the single job listing.

I can add this to the layout template, reading a custom Twig variable of socialImageUrl

<meta property="og:image" content="{{ url('jobs/' ~ socialImageUrl|default('myimage') ~ '.png') }}" />s

and then within the job listing template

{% set socialImageUrl = %}

Now if you save an entry in the control panel and then visit and use the URL for the entry listing, you'll see something like

And that's how I'm generating custom social cards per entry.


Leave me a message. If you like what you read here, there's a great chance we could be a good fit. If it's something I don't do, I'll likely know someone who does.