How I built TailGraph: a free API to generate OG images for Twitter and Facebook using TailwindCSS

How I built TailGraph: a free API to generate OG images for Twitter and Facebook using TailwindCSS

Hey folks! These past few days I've been working on this idea I had while creating yet another automatic OG image generation for a Laravel website.

The basic idea my customers wanted is that, whenever a link from their website is shared online, the OG image should display information about the link (i.e. the blog post title). Of course, this image must be branded with its own colors and logo.

The idea

As of today, I was using PHP code to generate the images, but this approach had a problem: if the text was too large it would not break into a new line. And I started thinking: "how can I make it in such a way that it would adapt to any kind of text?". Then, I thought about Puppeteer: I could launch a Chrome instance in an API to generate this picture and, in order to make it super customizable, use TailwindCSS to do it!


The decision was already made. I would create this Lambda function that would generate a new image based on a few parameters to set up the background, texts, colors...

I started a small node project in my local environment, installed puppeteer and tailwindcss, and started coding.

First step: customization

Which parameters would be customizable using this application? Well, I should be able to change the background (and even use an image), the company logo, a title, a small text, and an optional footer. I came up with this configuration:

const defaultProperties = {
        baseFontSize: 32,
        fontFamily: undefined,
        logoUrl: undefined,
        logoTailwind: 'h-16 w-16',
        bgUrl: undefined,
        bgTailwind: 'bg-gray-800',
        title: undefined,
        titleSize: '4xl',
        titleFontFamily: undefined,
        titleTailwind: 'text-gray-50',
        text: undefined,
        textSize: 'xl',
        textFontFamily: undefined,
        textTailwind: 'text-gray-100',
        overlay: false,
        overlayImage: undefined,
        overlayTailwind: 'bg-black bg-opacity-50',
        footer: undefined,
        footerTailwind: 'text-center text-sm text-gray-200',
        containerTailwind: undefined

Second step: The template

I created a super-basic HTML template, but it's highly customizable thanks to the power of TailwindCSS. I decided to use ejs to compile the templates, introducing the previous variables:

<html lang="xyz" class="h-full">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OG Generator</title>
    <link href="^2/dist/tailwind.min.css" rel="stylesheet">
<body class="h-full">
    <div class="w-og h-og text-center flex justify-center items-center relative bg-color">
        <div class="absolute left-0 top-0 w-full h-full"></div>
        <div class="relative">
            <img src="" alt="" class="block object-contain object-center">
            <h1>This would be the title</h1>
            <p>This would be the secondary text.</p>
        <p class="block absolute bottom-0 left-0 w-full p-2"></p>

Once the template was done, it was just a matter of using ejs to pass the parameters into the view to compile it.

Final step: Generating the image

After the template was done, and the parameters configured, it was just a matter of using puppeteer to generate the final image:

const puppeteer = require('puppeteer')
const ejs = require('ejs')
const fs = require('fs')

const renderTemplate = (name, data) => {
    return new Promise((resolve, reject) => {
        ejs.renderFile(name, data, (err, str) => {
            if (err) {
                return reject(err)


const templateHtml = await renderTemplate(template, { data })
const browser = await puppeteer.launch({
    defaultViewport: {
        width: 2400,
        height: 1260

const page = await browser.newPage()
await page.setContent(templateHtml)
await page.screenshot({
    path: 'output.jpg',
    quality: 100
await browser.close()

After this step, the image was already generated on my local computer! All I had to do is to configure an AWS Lambda endpoint to generate it and... voilá!


After this small tutorial, I just wanted to link to the final version of the solution. It's deployed using Vercel and it's built with a live-editor to rapidly generate your template :-)

Go to TailGraph website

See the website on GitHub

Hope you like it 🤩