Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Generate an Open Graph image in ASP.NET Core by rendering page data onto a nonzero-size canvas, encoding the result as PNG, JPEG, or WebP, publishing it at a stable public URL, and placing that URL in the page’s og:image metadata. The image-generation step and the image-delivery step are separate: SkiaSharp can create and encode pixels, while ASP.NET Core static-file middleware or an endpoint can make those bytes reachable by URL.

The complete pipeline

A reliable implementation has five stages:

  1. Prepare data: choose the title, author, category, brand colors, and any other values that belong in the graphic. Validate and constrain untrusted input before drawing it.
  2. Render: create a canvas with nonzero dimensions and draw the background, typography, logo, and page-specific values.
  3. Encode: convert the raster image to PNG, JPEG, or WebP.
  4. Deliver: save the bytes to a stable public path or return them from a dedicated response endpoint.
  5. Publish metadata: emit the required Open Graph properties and associate optional image metadata with the correct image.

Open Graph defines og:title, og:type, og:image, and og:url as required properties. When a page specifies an image, the protocol says it should also specify og:image:alt. Width, height, and MIME type are optional structured properties.

Choose an image architecture

Generate at request time

A route can render an image from current page data whenever a crawler requests it. This keeps content fresh and avoids storing files, but repeated requests consume rendering resources. Add input limits, an appropriate cache policy, and protection against unbounded text or expensive requests.

Pre-generate and serve a file

Generate during publishing or in a background job, then write the result under the web root. Static-file middleware serves the completed file efficiently. This approach makes cache behavior and failures easier to reason about, but you must handle regeneration, invalidation, storage, and stable naming.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Decision Request-time route Pre-generated file
Freshness Reflects current data when rendered Requires regeneration after changes
Compute Work occurs during requests Work occurs during publishing or jobs
Cacheability Requires response and intermediary caching Static assets are straightforward to cache
Storage Usually stores no image file Needs a storage location and cleanup policy
URL stability Stable route is possible Stable filename or versioning is required

Install and configure SkiaSharp

SkiaSharp provides the drawing abstraction used below. Its SKImage content is immutable, and the documented image or surface creation paths return no image when a requested dimension is zero. Validate width and height before allocating a bitmap or surface.

Add the SkiaSharp package appropriate to the .NET and operating-system combination used by your application. Package versions and native-runtime requirements vary, so pin and verify the version in your own project rather than assuming a version here.

dotnet add package SkiaSharp

The following service illustrates the core operation. It draws a dark background and wraps a title into several lines. The text, font family, dimensions, and colors are implementation choices; adjust them to your design and target consumers.

using SkiaSharp;

public sealed class OgImageRenderer
{
    public byte[] Render(string title, string? category)
    {
        const int width = 1200;
        const int height = 630;
        if (width <= 0 || height <= 0)
            throw new ArgumentOutOfRangeException("Image dimensions must be positive.");

        using var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
        using var canvas = new SKCanvas(bitmap);
        canvas.Clear(new SKColor(18, 24, 38));

        using var accent = new SKPaint { Color = new SKColor(72, 187, 255), IsAntialias = true };
        canvas.DrawRect(new SKRect(0, 0, width, 18), accent);

        using var categoryPaint = new SKPaint
        {
            Color = SKColors.LightSkyBlue,
            TextSize = 34,
            IsAntialias = true,
            Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold)
        };
        canvas.DrawText(category ?? "ARTICLE", 72, 100, categoryPaint);

        using var titlePaint = new SKPaint
        {
            Color = SKColors.White,
            TextSize = 64,
            IsAntialias = true,
            Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold)
        };
        var lines = Wrap(title ?? string.Empty, titlePaint, width - 144);
        var y = 205f;
        foreach (var line in lines.Take(4))
        {
            canvas.DrawText(line, 72, y, titlePaint);
            y += 78;
        }

        using var image = SKImage.FromBitmap(bitmap);
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);
        return data.ToArray();
    }

    private static IEnumerable<string> Wrap(string text, SKPaint paint, float maxWidth)
    {
        var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        var line = string.Empty;
        foreach (var word in words)
        {
            var candidate = line.Length == 0 ? word : $"{line} {word}";
            if (paint.MeasureText(candidate) <= maxWidth)
                line = candidate;
            else
            {
                if (line.Length > 0) yield return line;
                line = word;
            }
        }
        if (line.Length > 0) yield return line;
    }
}

Long words, missing fonts, right-to-left scripts, emoji, and text that exceeds four lines need deliberate handling. You can reduce type size, truncate with an ellipsis, reserve a fixed number of lines, or select a font with the required glyph coverage. Do not let page authors choose arbitrary font files or dimensions without limits.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Encode PNG, JPEG, or WebP

SkiaSharp’s encoding APIs support JPEG, PNG, and WebP, including stream-oriented overloads. PNG preserves transparency and is lossless. JPEG has no alpha channel, so flatten the design onto an opaque background before encoding. WebP can offer a smaller file with configurable quality and compression, but verify that every target consumer accepts the format you choose.

using var image = SKImage.FromBitmap(bitmap);
using var png = image.Encode(SKEncodedImageFormat.Png, 100);
await System.IO.File.WriteAllBytesAsync(path, png.ToArray());

// JPEG: use an opaque canvas, then choose a quality value.
using var jpeg = image.Encode(SKEncodedImageFormat.Jpeg, 85);

// WebP: quality is encoder-dependent; verify output with your consumers.
using var webp = image.Encode(SKEncodedImageFormat.Webp, 80);

Keep the extension, response content type, and encoded bytes consistent. Inspect the resulting bytes during testing rather than trusting a filename alone.

Publish a stable image URL

Static files from the web root

ASP.NET Core static-file middleware can serve files from the application’s web root at host-based URLs. In a typical application, save the generated file beneath wwwroot/og and enable static files in the request pipeline.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStaticFiles();
app.MapControllers();
app.Run();

A generated file at wwwroot/og/article-123.png can then be referenced as https://your-host.example/og/article-123.png, provided the host is publicly reachable and the file is not behind authentication.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Return the image from an endpoint

For request-time generation, expose a route that validates an identifier, loads the corresponding page data, renders the image, and returns the bytes with the correct content type. Keep the route deterministic so the same page has a stable URL and can be cached.

[ApiController]
public sealed class OgController : ControllerBase
{
    [HttpGet("/og/{slug}.png")]
    public IActionResult Get(string slug)
    {
        // Look up slug from a trusted data source and enforce length/character limits.
        var renderer = new OgImageRenderer();
        var bytes = renderer.Render("Example article", "Guides");
        Response.Headers.CacheControl = "public,max-age=3600";
        return File(bytes, "image/png");
    }
}

The example uses placeholder lookup data intentionally. In production, return not-found for an unknown slug, avoid reflecting unsanitized HTML into the image, and choose cache headers that match your invalidation strategy.

Emit Open Graph metadata

Render these tags in the page’s HTML <head>. Use an absolute URL that a crawler can reach without a login, VPN, or internal DNS. That public-access requirement is an engineering consequence of og:image being an image URL; verify the behavior of each platform you target.

<meta property="og:title" content="A page-specific title">
<meta property="og:type" content="website">
<meta property="og:url" content="https://example.com/article/example">
<meta property="og:image" content="https://example.com/og/example.png">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="A concise description of the image content">

Put structured properties immediately after their corresponding root image property. If you list multiple og:image values, repeat the associated type, dimensions, and alt properties in the intended order. The protocol recognizes width and height as pixel values but does not establish one universal mandatory size. Treat dimensions as a design decision and verify current requirements for every platform you support.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Sale
Programming ASP.NET Core (Developer Reference)
  • Applying all key ASP.NET Core components, including MVC for HTML generation, .NET Core, EF Core, ASP.NET Identity, dependency injection, and more
  • Integrating ASP.NET Core with leading client-side frameworks, including Bootstrap
  • ASP.NET Core code for implementing business logic and data transformations
  • Handling configuration, routing, controllers, views, and common tasks (including posting forms and presenting data)
  • Performing complementary tasks: error handling, logging, application design, authentication, localization, and more
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Validation and troubleshooting

The image is blank or missing

  • Confirm width and height are greater than zero before creating the bitmap or surface.
  • Check that drawing coordinates fall inside the canvas and that paint colors have visible alpha.
  • Open the returned bytes locally and verify they begin as a valid image, not an exception page or empty response.

The URL returns 404

  • Confirm the generated file is under the configured web root and that UseStaticFiles() runs in the pipeline.
  • Check filename casing, URL encoding, and deployment output.
  • For an endpoint, verify route constraints and that the request reaches the correct application.

The crawler cannot fetch it

  • Use the public HTTPS host, not localhost or an internal hostname.
  • Remove authentication requirements and check firewall, robots, proxy, and certificate behavior.
  • Inspect the image response status, content type, and cache headers from outside your network.

Text is clipped or unreadable

  • Measure each candidate line before drawing it and apply a maximum line count.
  • Reserve space for category labels, logos, and safe margins.
  • Install or package a font with the glyphs your content requires, and test non-Latin text.

Updates do not appear in previews

Ensure the URL changes when an image must be invalidated, or use an appropriate cache policy. Social previews can be cached independently; consult the current documentation for each target platform before relying on a particular refresh procedure.

Test the complete path

  1. Render a page and inspect its final HTML, not only a server-side template.
  2. Verify all four required properties and an alt value for every declared image.
  3. Request the image URL from an unauthenticated external client.
  4. Check status, Content-Type, byte length, and that an image decoder can open the bytes.
  5. Test short, long, empty, multilingual, and punctuation-heavy titles.
  6. Re-run after deployment because native image dependencies, fonts, proxy rules, and static-file paths can differ between environments.

Or skip the browser setup

If your actual need is a clean screenshot of an existing page rather than a custom server-side graphic, ScreenshotNeo provides a one-call website screenshot API. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing result in headers.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for parameters and response details. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Every feature is available on every plan; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Does Open Graph require a particular image size?

The protocol documentation identifies width and height as optional pixel metadata but does not define one universal size. Choose dimensions for your design and verify current requirements for each platform you target.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I generate the image in a controller or a background job?

Use request-time generation when freshness and personalization matter; use pre-generation when predictable latency, static delivery, and simpler caching are more important.

Can I use JPEG when my design has transparency?

No. JPEG does not preserve alpha transparency. Flatten the design onto an opaque background or use PNG or a compatible WebP configuration.

Quick Recap

Bestseller No. 2
SaleBestseller No. 5
Programming ASP.NET Core (Developer Reference)
Programming ASP.NET Core (Developer Reference)
Integrating ASP.NET Core with leading client-side frameworks, including Bootstrap; ASP.NET Core code for implementing business logic and data transformations
$24.99

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.