install
source · Clone the upstream repo
git clone https://github.com/Aradotso/trending-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aradotso/trending-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/openbrand-asset-extraction" ~/.claude/skills/aradotso-trending-skills-openbrand-asset-extraction && rm -rf "$T"
manifest:
skills/openbrand-asset-extraction/SKILL.mdsource content
--- name: openbrand-asset-extraction description: Extract brand assets (logos, colors, backdrops, brand name) from any website URL using OpenBrand triggers: - extract brand assets from a website - get logo and colors from a URL - fetch brand colors from a website - extract favicon and brand name from a domain - get og image and brand assets - pull brand kit from a website URL - extract brand assets like Brand.dev - get brand logo colors backdrop from URL --- # OpenBrand Asset Extraction > Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. OpenBrand extracts brand assets — logos, colors, backdrop images, and brand name — from any website URL. It is an open-source alternative to Brand.dev, available as an npm package, REST API, MCP server, or agent skill. ## Installation ### npm package (no API key required) ```bash npm add openbrand # or bun add openbrand
MCP server for Claude Code / Cursor
# Without API key (uses local extraction) claude mcp add --transport stdio openbrand -- npx -y openbrand-mcp # With API key (uses hosted API) claude mcp add --transport stdio \ --env OPENBRAND_API_KEY=$OPENBRAND_API_KEY \ openbrand -- npx -y openbrand-mcp
Or add to
.claude/settings.json:
{ "mcpServers": { "openbrand": { "command": "npx", "args": ["-y", "openbrand-mcp"], "env": { "OPENBRAND_API_KEY": "your_api_key_here" } } } }
Agent skill (Claude Code, Cursor, Codex, Gemini CLI)
npx skills add ethanjyx/openbrand
Self-host the web app
git clone https://github.com/ethanjyx/openbrand.git cd openbrand bun install bun dev # Open http://localhost:3000
No environment variables required for local dev.
npm Package Usage
Basic extraction
import { extractBrandAssets } from "openbrand"; const result = await extractBrandAssets("https://stripe.com"); if (result.ok) { const { brand_name, logos, colors, backdrop_images } = result.data; console.log("Brand name:", brand_name); // "Stripe" console.log("Logos:", logos); // LogoAsset[] console.log("Colors:", colors); // ColorAsset[] console.log("Backdrops:", backdrop_images); // BackdropAsset[] } else { console.error("Error code:", result.error.code); // "ACCESS_BLOCKED" | "NOT_FOUND" | "SERVER_ERROR" | ... console.error("Error message:", result.error.message); // human-readable explanation }
Working with logos
import { extractBrandAssets } from "openbrand"; const result = await extractBrandAssets("https://github.com"); if (result.ok) { const { logos } = result.data; // Find the highest-resolution logo const best = logos.reduce((a, b) => (a.width ?? 0) * (a.height ?? 0) >= (b.width ?? 0) * (b.height ?? 0) ? a : b ); console.log("Best logo URL:", best.url); console.log("Dimensions:", best.width, "x", best.height); console.log("Format:", best.format); // "svg" | "png" | "ico" | ... console.log("Source:", best.source); // "favicon" | "apple-touch-icon" | "nav-logo" | "inline-svg" }
Working with colors
import { extractBrandAssets } from "openbrand"; const result = await extractBrandAssets("https://linear.app"); if (result.ok) { const { colors } = result.data; colors.forEach((color) => { console.log("Hex:", color.hex); // "#5E6AD2" console.log("Source:", color.source); // "theme-color" | "manifest" | "logo-dominant" }); // Get the primary brand color const primary = colors.find((c) => c.source === "theme-color") ?? colors[0]; console.log("Primary color:", primary?.hex); }
Working with backdrop images
import { extractBrandAssets } from "openbrand"; const result = await extractBrandAssets("https://vercel.com"); if (result.ok) { const { backdrop_images } = result.data; backdrop_images.forEach((img) => { console.log("URL:", img.url); console.log("Source:", img.source); // "og:image" | "css-background" | "hero-image" console.log("Alt:", img.alt); }); // Get the OG image (best for social previews) const ogImage = backdrop_images.find((img) => img.source === "og:image"); }
Error handling patterns
import { extractBrandAssets } from "openbrand"; async function getBrandSafely(url: string) { const result = await extractBrandAssets(url); if (!result.ok) { switch (result.error.code) { case "ACCESS_BLOCKED": // Site blocks scrapers — try a different approach or skip console.warn("Site blocks automated access:", url); return null; case "NOT_FOUND": console.warn("URL not found:", url); return null; case "SERVER_ERROR": console.error("Target server error:", result.error.message); return null; default: console.error("Unknown error:", result.error.message); return null; } } return result.data; }
Batch extraction
import { extractBrandAssets } from "openbrand"; const urls = [ "https://stripe.com", "https://github.com", "https://vercel.com", "https://linear.app", ]; // Parallel extraction with error resilience const results = await Promise.allSettled( urls.map((url) => extractBrandAssets(url)) ); const brands = results .map((r, i) => ({ url: urls[i], result: r })) .filter(({ result }) => result.status === "fulfilled" && (result as PromiseFulfilledResult<any>).value.ok) .map(({ url, result }) => ({ url, ...(result as PromiseFulfilledResult<any>).value.data, })); console.log("Successfully extracted:", brands.length, "brands");
REST API Usage
Get a free API key at openbrand.sh/dashboard.
cURL
curl "https://openbrand.sh/api/extract?url=https://stripe.com" \ -H "Authorization: Bearer $OPENBRAND_API_KEY"
TypeScript fetch
async function extractBrand(url: string) { const res = await fetch( `https://openbrand.sh/api/extract?url=${encodeURIComponent(url)}`, { headers: { Authorization: `Bearer ${process.env.OPENBRAND_API_KEY}`, }, } ); if (!res.ok) { throw new Error(`API error: ${res.status} ${res.statusText}`); } return res.json(); } const brand = await extractBrand("https://stripe.com"); console.log(brand.brand_name); // "Stripe"
Python requests
import requests import os def extract_brand(url: str) -> dict: res = requests.get( "https://openbrand.sh/api/extract", params={"url": url}, headers={"Authorization": f"Bearer {os.environ['OPENBRAND_API_KEY']}"}, ) res.raise_for_status() return res.json() brand = extract_brand("https://stripe.com") print(brand["brand_name"]) # "Stripe" print(brand["colors"]) # list of color assets
What Gets Extracted
| Asset | Sources |
|---|---|
| Logos | favicons, apple-touch-icons, header/nav logos, inline SVGs (with dimension probing) |
| Brand colors | meta tag, , dominant colors from logo imagery |
| Backdrop images | , CSS backgrounds, hero/banner images |
| Brand name | , , logo alt text, page |
Common Patterns
Building a brand card component
import { extractBrandAssets } from "openbrand"; interface BrandCard { name: string; logoUrl: string | null; primaryColor: string | null; ogImage: string | null; } async function buildBrandCard(url: string): Promise<BrandCard | null> { const result = await extractBrandAssets(url); if (!result.ok) return null; const { brand_name, logos, colors, backdrop_images } = result.data; // Prefer SVG, then largest raster const logo = logos.find((l) => l.format === "svg") ?? logos.sort( (a, b) => (b.width ?? 0) * (b.height ?? 0) - (a.width ?? 0) * (a.height ?? 0) )[0] ?? null; const primaryColor = colors.find((c) => c.source === "theme-color")?.hex ?? colors[0]?.hex ?? null; const ogImage = backdrop_images.find((img) => img.source === "og:image")?.url ?? null; return { name: brand_name ?? new URL(url).hostname, logoUrl: logo?.url ?? null, primaryColor, ogImage, }; }
Enriching a list of companies
import { extractBrandAssets } from "openbrand"; const companies = [ { name: "Stripe", website: "https://stripe.com" }, { name: "Vercel", website: "https://vercel.com" }, ]; const enriched = await Promise.all( companies.map(async (company) => { const result = await extractBrandAssets(company.website); if (!result.ok) return { ...company, brand: null }; return { ...company, brand: { logo: result.data.logos[0]?.url ?? null, color: result.data.colors[0]?.hex ?? null, }, }; }) );
Tech Stack (for self-hosting contributors)
- Framework: Next.js + React
- Language: TypeScript
- HTML parsing: Cheerio
- Image processing: Sharp
- Styling: Tailwind CSS
- Runtime: Bun (recommended) or Node.js
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
error | Site blocks scrapers (Cloudflare, etc.) | Use the hosted API which has bypass strategies; nothing the npm package can do |
Empty array | Site uses JS-rendered logos | Consider the hosted API which may handle JS rendering |
error | Invalid URL or domain doesn't resolve | Verify the URL is reachable in a browser |
| Colors array is empty | No meta, no manifest, no logo to analyze | Expected for minimal sites |
| MCP tool not appearing | MCP server not installed | Run to verify installation |
Links
- Web app: openbrand.sh
- API dashboard: openbrand.sh/dashboard
- npm package: npmjs.com/package/openbrand
- MCP package: npmjs.com/package/openbrand-mcp
- Agent skill: skills.sh/ethanjyx/openbrand
- GitHub: github.com/ethanjyx/openbrand
- License: MIT