On this page

    Build features

    Add Social Share Images to Every Page

    Give every page an og:image without an image pipeline: create a signing key pair, sign a screenshot URL in your template, add the meta tags.

    Time
    20 minutes
    Plan
    Free
    You need
    • An API key in RS_API_KEY
    • A server-side template (Express, Django, Rails, Laravel or similar)

    What you will have at the end

    When someone shares your link, the chat app or social network shows the screenshot as the preview image.

    A share image is the picture that Slack, LinkedIn, Facebook, Discord and X show when someone posts your link. You will make it from a screenshot of the page. You do not store any files: the image URL in your HTML asks RenderScreenshot to render the page the first time a crawler fetches it.

    The URL is signed: it carries a public key ID and a signature, never your API key. Nobody can change its options or use it to take other screenshots.

    Create a signing key pair

    Open API Keys and click Create Key. Enter a name for the site, for example Website og:image, and select Signing key pair.

    Make one pair per site. If you revoke a pair, only that site's image URLs stop working.

    Click Create Key. The dashboard shows two values one time only. Copy both.

    rs_pub_ goes into public URLs. rs_secret_ stays on your server and signs them.

    Save the keys on your server

    Add the two values to your server's environment, next to your API key:

    RS_API_KEY=rs_live_...         # server requests
    RS_PUBLIC_KEY_ID=rs_pub_...    # public: goes into the image URL
    RS_SIGNING_KEY=rs_secret_...   # secret: signs the image URL
    

    Never send RS_SIGNING_KEY or RS_API_KEY to the browser. Only the finished, signed URL goes into your HTML.

    Sign the image URL in your template

    The examples below sign a URL for the page itself with the og_card preset (1200×630). They also turn on caching for 30 days, so repeat crawler fetches return the first render for 0 credits.

    The expiry changes once a week, not on every request. A URL that changes on every request looks like a new image to the social networks, so they would fetch it again and again.

    Node.js

    import { Client, TakeOptions } from 'renderscreenshot';
    
    const DAY = 24 * 60 * 60 * 1000;
    
    const client = new Client(process.env.RS_API_KEY, {
      signingKey: process.env.RS_SIGNING_KEY,
      publicKeyId: process.env.RS_PUBLIC_KEY_ID,
    });
    
    // The same expiry for 7 days, always 21 to 28 days ahead.
    function windowedExpiry(now = Date.now()) {
      const start = Math.floor(now / (7 * DAY)) * 7 * DAY;
      return new Date(start + 28 * DAY);
    }
    
    export function ogImageUrl(pageUrl) {
      return client.generateUrl(
        TakeOptions.url(pageUrl).preset('og_card').cacheAvailable().cacheTtl(2592000),
        windowedExpiry(),
      );
    }
    

    Python

    import os
    import time
    
    from renderscreenshot import Client, TakeOptions
    
    DAY = 24 * 3600
    
    client = Client(
        os.environ['RS_API_KEY'],
        signing_key=os.environ['RS_SIGNING_KEY'],
        public_key_id=os.environ['RS_PUBLIC_KEY_ID'],
    )
    
    
    def windowed_expiry(now=None):
        """The same expiry for 7 days, always 21 to 28 days ahead."""
        now = time.time() if now is None else now
        start = int(now // (7 * DAY)) * 7 * DAY
        return start + 28 * DAY
    
    
    def og_image_url(page_url):
        options = TakeOptions.url(page_url).preset('og_card').cache_available().cache_ttl(2592000)
        return client.generate_url(options, windowed_expiry())
    

    Ruby (Rails helper)

    # app/helpers/og_image_helper.rb
    module OgImageHelper
      DAY = 24 * 3600
    
      OG_CLIENT = RenderScreenshot::Client.new(
        ENV.fetch('RS_API_KEY'),
        signing_key: ENV.fetch('RS_SIGNING_KEY'),
        public_key_id: ENV.fetch('RS_PUBLIC_KEY_ID')
      )
    
      # The same expiry for 7 days, always 21 to 28 days ahead.
      def self.windowed_expiry(now = Time.now)
        start = (now.to_i / (7 * DAY)) * 7 * DAY
        Time.at(start + (28 * DAY))
      end
    
      def og_image_url(page_url)
        options = RenderScreenshot::TakeOptions.url(page_url).preset('og_card').cache_available.cache_ttl(2_592_000)
        OG_CLIENT.generate_url(options, expires_at: OgImageHelper.windowed_expiry)
      end
    end
    

    PHP, Go and Java versions, and a version for static sites, are in signed URLs in og:image tags.

    Add the meta tags

    Put the signed URL in the page's <head>. Your template engine escapes each & as &amp;, which is correct.

    <meta property="og:image" content="https://api.renderscreenshot.com/v1/screenshot?cache_available=true&amp;cache_ttl=2592000&amp;expires=...&amp;key_id=rs_pub_...&amp;preset=og_card&amp;url=...&amp;signature=...">
    <meta property="og:image:width" content="1200">
    <meta property="og:image:height" content="630">
    <meta property="og:image:alt" content="Launch week: five new features">
    <meta name="twitter:card" content="summary_large_image">
    

    In Rails, with the helper from step 3:

    <%= tag.meta property: 'og:image', content: og_image_url(request.original_url) %>
    <%= tag.meta property: 'og:image:width', content: 1200 %>
    <%= tag.meta property: 'og:image:height', content: 630 %>
    

    Check the result

    Copy the signed URL from your page source and open it once. That first fetch renders the image, so it uses one credit:

    curl -sS -o card.png -w '%{http_code} %{content_type}\n' "$SIGNED_URL"
    # 200 image/png
    
    The share image for a blog post: the top of the page at 1200×630. Repeat fetches within 30 days cost 0 credits.

    Then paste your page URL into a link preview checker, or post it in a private Slack or Discord channel. The card shows the image, the title and the description from your other meta tags.

    You see Cause Fix
    No image The URL returns 401 The signature or expiry is wrong. Sign with the same options you put in the URL.
    An old image The platform cached the first fetch Use the platform's refresh tool, or change the page URL.
    The page looks wrong Cookie banner, lazy images Add the fixes from remove cookie banners to the signed options.

    Make a designed card instead

    The page itself is the simplest card. For a card with a big title and your logo, make a small route on your site, for example /og/launch-week, that lays out the title at 1200×630, and sign that URL instead. Generate OG images from HTML walks through a full card template.

    Next steps

    Try RenderScreenshot free

    150 free credits every month, no credit card required.