A .NET library for extracting link preview metadata (OpenGraph, Twitter Card, oEmbed) from URLs.
.NET CLI
dotnet add package UrlPreviewParserPackage Manager
Install-Package UrlPreviewParserPackageReference
<PackageReference Include="UrlPreviewParser" Version="1.1.0" />using UrlPreviewParser;
using var client = new UrlPreviewClient();
var result = await client.GetPreviewAsync("https://example.com");
// OpenGraph
Console.WriteLine(result.OpenGraph.Title);
Console.WriteLine(result.OpenGraph.Description);
Console.WriteLine(result.OpenGraph.Image);
// Twitter Card
Console.WriteLine(result.TwitterCard.Title);
Console.WriteLine(result.TwitterCard.Image);
// Basic page title
Console.WriteLine(result.Tags.Title);og:* tags are read from both the property and the name attribute; when a key appears more than once
the first occurrence wins (e.g. the first og:image).
OpenGraphData extends Dictionary<string, string>. Any og:* property is accessible by key
(e.g., result.OpenGraph["og:title"]). Convenience properties cover the most common fields:
| Property | Key |
|---|---|
Title |
og:title |
Description |
og:description |
Image |
og:image |
Url |
og:url |
Type |
og:type |
SiteName |
og:site_name |
twitter:* tags are read from both the name and the property attribute (first occurrence wins).
TwitterCardData extends Dictionary<string, string>. Any twitter:* property is accessible
by key (e.g., result.TwitterCard["twitter:label1"]). Convenience properties:
| Property | Key | Notes |
|---|---|---|
Card |
twitter:card |
e.g. summary, summary_large_image |
Title |
twitter:title |
|
Description |
twitter:description |
|
Image |
twitter:image |
Falls back to twitter:image:src |
Url |
twitter:url |
|
Site |
twitter:site |
@username of the website |
Creator |
twitter:creator |
@username of the content creator |
Discovered oEmbed endpoint URLs and the raw response body:
| Property | Type | Description |
|---|---|---|
Json |
string? |
URL of the JSON oEmbed endpoint |
Xml |
string? |
URL of the XML oEmbed endpoint |
Formats |
List<string> |
Discovered format names (e.g. "json", "xml") |
Body |
string? |
Raw response body from the oEmbed endpoint |
Error |
string? |
Error message if the endpoint fetch failed |
| Property | Type | Description |
|---|---|---|
Title |
string? |
Content of the first HTML <title> element |
Images |
List<string> |
Image URLs found on the page |
Description |
string? |
<meta name="description"> |
Canonical |
string? |
<link rel="canonical"> |
Favicon |
string? |
First rel="icon" (incl. shortcut icon), else apple-touch-icon, mask-icon, … |
Language |
string? |
lang attribute of <html> |
Charset |
string? |
<meta charset> or <meta http-equiv="Content-Type"> |
Viewport |
string? |
<meta name="viewport"> |
Robots |
string? |
<meta name="robots"> |
ThemeColor |
string? |
<meta name="theme-color"> |
Generator |
string? |
<meta name="generator"> |
result.Head is a List<Dictionary<string, string?>> containing the raw attributes of every
child element of <head>. Useful when you need access to tags not covered by the typed properties.
Values in Head are never modified (relative URLs stay relative).
result.Url is the requested URL; result.FinalUrl is the URL actually parsed after redirects.
By default relative URLs in URL-valued properties (og:image, og:url, og:video, og:audio and their
:url/:secure_url variants, twitter:image, twitter:image:src, twitter:player, twitter:url,
Tags.Canonical, Tags.Favicon, oEmbed endpoints) are resolved to absolute URLs against <base href>
or FinalUrl. Absolute values (including data: URIs) and non-URL values such as og:image:width are
left untouched.
var options = new UrlPreviewOptions
{
Timeout = TimeSpan.FromSeconds(10), // default 15 s; null = only HttpClient.Timeout
MaxContentLength = 2 * 1024 * 1024, // default 5 MB; the rest of the body is ignored
FetchOembed = false, // default true
ResolveRelativeUrls = true, // default true
};
using var client = new UrlPreviewClient(httpClient, options);When the timeout elapses GetPreviewAsync throws a TimeoutException; non-success status codes
throw an HttpRequestException (with StatusCode set on .NET 5+).
The body is decoded with the charset from the Content-Type header, a byte order mark, or a
<meta charset> in the first 1024 bytes, falling back to UTF-8. To support legacy code pages such as
windows-1252 on .NET, register the provider once at startup:
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);When using dependency injection, pass your HttpClient directly. The caller is responsible for
its lifetime; UrlPreviewClient will not dispose it.
// ASP.NET Core — register via IHttpClientFactory
builder.Services.AddHttpClient<MyService>();
// Then inject and use
public class MyService(IHttpClientFactory factory)
{
public async Task<UrlPreviewResult> GetPreviewAsync(string url, CancellationToken ct)
{
var httpClient = factory.CreateClient();
using var client = new UrlPreviewClient(httpClient);
return await client.GetPreviewAsync(url, ct);
}
}ParseAsync parses metadata from an HTML string without making any HTTP requests. The oEmbed
endpoint is discovered but not fetched.
using var client = new UrlPreviewClient();
var result = await client.ParseAsync("https://example.com", htmlString);MIT — see LICENSE for details.