Back to Articles

How to create a code screenshot for social media

· 8 min read
How to create a code screenshot for social media

You have written a neat bit of code and you want to post it. Maybe it is a clever one-liner for Twitter, a worked example for a LinkedIn post or the hero image for a dev.to article. So you do one of two things, and both are bad.

You paste the code as text. The platform strips the highlighting, mangles the indentation, wraps the long lines and turns your && into a smart-quoted mess. Or you screenshot your editor. Now the image carries your personal theme, a stray notification, half a file tree and whatever odd dimensions your window happened to be. Do it twice and the two images do not match.

A code screenshot is a real asset, so it deserves to be generated like one: consistent, on brand, sharp on retina screens, sized for the platform and repeatable from a script. This article covers three ways to produce them, from a no-code tool through to a single API call you can drop into your build.

What separates a good code image from a bad one

Before the how, the what. A code screenshot that performs on social shares a few qualities.

It is readable at thumbnail size. Most people see your post in a crowded feed at a fraction of full size, so the type has to be large and the contrast high. It keeps its syntax highlighting, because colour is what tells the eye where the strings, keywords and function names are. It has consistent padding and a deliberate background instead of a flush-to-the-edge crop. It matches the aspect ratio the platform wants, so nothing important gets cropped in the preview. It is rendered at double resolution so it stays crisp when the timeline scales it. And it carries none of the clutter that comes free with an editor screenshot: no tab bar, no minimap, no Slack badge in the corner.

Hit those and your snippet looks like it belongs to a publication rather than a debugging session. The three approaches below all get you there. Pick by how much control you want and whether you need to automate.

The fastest route: the browser tool

For a one-off, you do not need code at all. The code screenshot generator is a browser tool: paste your snippet, choose the language, pick a theme and background and download the PNG. It runs the same renderer the API does, so what you see is what you ship.

This is the right tool when you are writing a single post and want it done in under a minute. It stops being the right tool the moment you want the same look across twenty snippets, or you want to generate an image automatically when you publish. For that, you want the API.

Generating code screenshots from the API

The code screenshot template takes a JSON payload and returns a hosted PNG. You send the code and a few style options, and you get back a CDN URL. No browser to drive, no headless Chrome to keep alive, no fonts to install on a server.

The endpoint is a single POST. Authentication is an X-API-Key header, which you can issue and rotate from your dashboard (see the authentication docs). Here is the smallest request that does something useful:

curl -X POST https://app.html2img.com/api/v1/templates/code-screenshot \
  -H "X-API-Key: $HTML2IMG_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "const sum = (a, b) => a + b;",
    "language": "javascript"
  }'

The response tells you where the image lives:

{
  "success": true,
  "id": "abc123",
  "url": "https://i.html2img.com/abc123.png",
  "credits_remaining": 1234,
  "template": "code-screenshot"
}

That url is the finished image. Drop it straight into an og:image tag, a tweet or a Markdown file. The one call below produces this:

A TypeScript function rendered as a code screenshot with macOS window chrome on a purple gradient background

The inputs

Only code is required. Everything else shapes the look.

Input

Type

Description

code

string

The source to render. Required. Newlines as \n.

language

string

Language for syntax highlighting, e.g. typescript, python, go.

title

string

Text in the window title bar, usually a filename like src/lib/users.ts.

theme

string

A highlighting theme, e.g. atom-one-dark, github, dracula.

background

string

Any CSS background: a solid colour or a gradient behind the window.

padding

number

Space in pixels between the window and the edge of the image.

show_window_chrome

true or false

Toggle the macOS traffic-light title bar.

show_line_numbers

true or false

Toggle the line-number gutter.

The image renders at 1600×1000 by default, which is a 16:10 frame that downscales cleanly to the preview sizes the social platforms use.

The same call in your language

The pattern is identical everywhere: POST JSON with the key in the header, read url off the response. A few of the common runtimes follow. The full set, including Laravel, Rails, React and Vue, is in the usage guides.

Node, with the native fetch:

const res = await fetch("https://app.html2img.com/api/v1/templates/code-screenshot", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.HTML2IMG_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    code: "export const sum = (a, b) => a + b;",
    language: "typescript",
    title: "src/lib/maths.ts",
    theme: "atom-one-dark",
  }),
});

const { url } = await res.json();
console.log(url);

Python, with requests:

import os
import requests

resp = requests.post(
    "https://app.html2img.com/api/v1/templates/code-screenshot",
    headers={"X-API-Key": os.environ["HTML2IMG_KEY"]},
    json={
        "code": "def sum(a, b):\n    return a + b",
        "language": "python",
        "title": "maths.py",
        "theme": "atom-one-dark",
    },
)
resp.raise_for_status()
print(resp.json()["url"])

PHP, using Laravel's HTTP client:

use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-API-Key' => config('services.html2img.key'),
])->post('https://app.html2img.com/api/v1/templates/code-screenshot', [
    'code' => "public function sum(int \$a, int \$b): int\n{\n    return \$a + \$b;\n}",
    'language' => 'php',
    'title' => 'app/Support/Maths.php',
    'theme' => 'atom-one-dark',
]);

$url = $response->json('url');

Theming the screenshot to match your brand

The defaults are tasteful, but a code image that carries your colours is the one people start to recognise as yours. Four inputs do the work: theme, background, show_window_chrome and show_line_numbers.

The theme controls the syntax colours inside the window. Dark themes like atom-one-dark and dracula read well on most feeds. A light theme suits a clean, editorial look and pairs nicely with line numbers turned on for tutorial content:

A Python function on a light GitHub theme with line numbers, on a blue to teal gradient

The background sits behind the window and accepts any CSS value. A solid colour keeps things calm; a gradient gives the image some energy in a busy timeline. Match it to your site or your brand palette so a string of posts hangs together:

{
  "code": "type Status = \"idle\" | \"loading\" | \"done\";",
  "language": "typescript",
  "title": "types.ts",
  "theme": "dracula",
  "background": "linear-gradient(135deg, #22D3EE 0%, #6366F1 100%)",
  "show_line_numbers": "true"
}

A React component on the Dracula theme with line numbers, on a cyan to indigo gradient

When you want the code to be the whole story, turn the chrome off, drop the padding and set a flat background. The result is minimal and works well for short snippets where the window frame would only add noise:

A short curl command rendered with no window chrome on a flat dark navy background

A few choices that tend to hold up: keep snippets short, because a screenshot is for the highlight, not the whole file. Set the title to a real path so the image carries context. Pick one theme and one background and reuse them, so your posts read as a set rather than a grab bag.

Sizing for each platform

The template renders at 1600×1000, and that single frame covers most of what you need because the platforms downscale it.

Twitter and X show link previews and inline images at roughly 2:1 or 16:9, and a 16:10 source crops gracefully into both. LinkedIn favours something close to 1.91:1 for shared links, again well served by the default. For a dev.to cover image the platform wants 1000×420, so a wide code image reads fine as the banner with the snippet centred. Instagram is the awkward one, since it prefers square or 4:5, so for a feed post you are usually better building a square card with the raw HTML route below.

The default also renders at a resolution that stays sharp when a retina display scales it down, which is the single biggest reason an editor screenshot looks soft on a phone. If you need to push dimensions or device pixel ratio beyond what the template exposes, that is the cue to drop down a level, which is the next section. The DPI parameter docs cover the detail.

Automating it: generate screenshots in your build

The real payoff is not making one image, it is never thinking about it again. Because the whole thing is one HTTP call, you can wire it into a script or a CI step.

Here is a small Node script that turns any source file into a screenshot. Point it at a file and it prints the URL:

import { readFileSync } from "node:fs";

const path = process.argv[2];
const code = readFileSync(path, "utf8");

const res = await fetch("https://app.html2img.com/api/v1/templates/code-screenshot", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.HTML2IMG_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    code,
    language: "typescript",
    title: path,
    theme: "atom-one-dark",
    background: "linear-gradient(135deg, #6366F1 0%, #EC4899 100%)",
  }),
});

const { url } = await res.json();
console.log(url);

Run it with node screenshot.js src/index.ts and you have a branded image of that file in one step.

The same idea fits a release pipeline. This GitHub Actions job renders a snippet whenever you publish a release, ready to attach to the announcement:

name: Share snippet
on:
  release:
    types: [published]

jobs:
  screenshot:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Render the highlight
        env:
          HTML2IMG_KEY: ${{ secrets.HTML2IMG_KEY }}
        run: |
          curl -sX POST https://app.html2img.com/api/v1/templates/code-screenshot \
            -H "X-API-Key: $HTML2IMG_KEY" \
            -H "Content-Type: application/json" \
            -d @snippet.json \
            | jq -r '.url'

Because there is no browser in this loop, the job stays fast and cheap, and it will not fall over the way a headless Chrome step does when a font is missing or memory runs short. If you have ever fought that setup, the Puppeteer and Lambda alternative write-up covers why moving the rendering off your own infrastructure is usually the better trade.

When you need full control: render your own HTML

The template is deliberately opinionated. When you want something it does not offer, a custom font, a watermark, a two-up diff, your logo in the corner or a square Instagram card, you render your own HTML instead and post it to the HTML endpoint. You design the card once in HTML and CSS, and the API turns it into a PNG at whatever dimensions and DPI you ask for.

A minimal code card looks like this:

<div class="window">
  <div class="bar">
    <span class="dot red"></span>
    <span class="dot amber"></span>
    <span class="dot green"></span>
    <span class="file">greeting.go</span>
  </div>
  <pre><code>func Greet(name string) string {
    return fmt.Sprintf("Hello, %s", name)
}</code></pre>
</div>

Style it with your own fonts and colours, then send it off, setting dpi to 2 for a retina-sharp result and the dimensions to whatever the platform wants:

curl -X POST https://app.html2img.com/api/html \
  -H "X-API-Key: $HTML2IMG_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<div class=\"window\">...</div>",
    "width": 1080,
    "height": 1080,
    "dpi": 2
  }'

The trade is that syntax highlighting is now your job: you either ship pre-highlighted markup or inline a highlighter in the page. You also get total command of the design in return, which is exactly what you want for a square card or a branded template you reuse. If you would rather see the bare mechanics first, the screenshot with cURL guide walks the HTML endpoint from scratch. And if what you are really after is a repo card rather than a loose snippet, the GitHub social preview template renders one from JSON the same way the code template does.

Which one to reach for

Use the browser tool for a one-off you want done now. Use the code screenshot template when you want consistent, branded images and especially when you want to generate them from a script or on publish. Drop to raw HTML when the design needs to be exactly yours. All three lean on the same renderer, so you can start with the tool and graduate to the API without your images changing character.


Need code screenshots, OG images, certificates or invoices rendered from HTML without running a browser yourself? Browse the templates gallery or read the docs to get started.

Mike Griffiths

Mike has spent the last 20 years crafting software solutions for all kinds of amazing businesses. He specializes in building digital products and APIs that make a real difference. As an expert in Laravel and a voting member on the PHP language, Mike helps shape the future of web development.