Skip to main content
Job boards, marketplaces, and directories render the same card thousands of times, and every card needs the employer’s logo. At that volume, nobody can collect logos by hand. A candidate scrolls the feed and every card shows its employer’s logo, even listings from companies they’ve never heard of. Each logo is a Logo API image URL built from the domain on the listing, so new employers get logos automatically.

Demo

Here’s the card rendering four sample listings: The listings are sample data, but every employer logo loads live from img.logo.dev. Select a listing to open a company card with the employer’s description and social links.

Build it with AI

Want employer logos on your listing cards? This prompt gives your AI coding tool everything it needs to build it in your framework.

Open in Cursor

Code

This example is in React, but you can get the same result in any frontend framework: the card is one image URL next to fields already on your listing. Paste JobCard.tsx, then render it as the Usage tab shows. The logo slot is a fixed 40px square, so cards keep their height while images load.
type Job = {
  role: string;
  company: string;
  domain: string;
  location: string;
  posted: string;
};

export function JobCard({ job, token }: { job: Job; token: string }) {
  return (
    <div className="flex items-center gap-3 rounded-xl border border-zinc-950/10 bg-white p-4">
      <img
        alt={`${job.company} logo`}
        className="h-10 w-10 rounded-lg object-contain"
        height={40}
        loading="lazy"
        src={`https://img.logo.dev/${job.domain}?token=${token}&size=40&retina=true&format=webp`}
        width={40}
      />
      <div className="min-w-0">
        <div className="truncate text-sm font-semibold">{job.role}</div>
        <div className="truncate text-xs text-zinc-500">
          {job.company} · {job.location}
        </div>
      </div>
      <span className="ml-auto shrink-0 text-xs text-zinc-400">
        {job.posted}
      </span>
    </div>
  );
}
Your publishable key is built for client-side code, so you can ship it in the browser as-is.

How it works

  • Every employer logo is one image URL. img.logo.dev/:domain turns the domain on the listing into a logo, and query parameters set the size and format. See all image parameters.
  • A feed of ten thousand employers never breaks. When Logo.dev doesn’t have a logo, it returns a generated monogram instead of a broken image, so there are no per-company checks. See fallback images.
  • The CDN absorbs the volume. Logos are cached and served like any other image, so rendering the same card thousands of times costs normal image requests.
  • The card is plain markup. Logos load as cards scroll into view and the image slot keeps a fixed size, so the feed never jumps. The component above carries the details.

Make it your own

  • Reuse the card for any company list. Swap the job fields for a one-line description and the same card serves VC portfolios and directories.
  • Resolve employers without domains. Let posters pick their company with the autocomplete, or look up logos by name.
  • Restyle the logos. Add &greyscale=true for a uniform feed or &theme=dark for dark backgrounds. See all image parameters.

Go further: build a company card

The domain that loads a logo can also pull a description and social profiles. Resolve the employer once by name, then show that brand data on a company card next to the listing. You can turn a company name into full brand data with one server route. It calls the Search API to resolve the name to a domain, then the Describe API to read the description, brand colors, and social profiles. Both use your secret key and the Describe API needs a paid plan, so this code runs on your server and the browser only ever receives the finished result.
const BASE = "https://api.logo.dev";
const headers = { Authorization: `Bearer ${process.env.LOGO_DEV_SECRET_KEY}` };

// Resolve a typed company name to a domain, then enrich it with brand data.
export async function GET(request: Request) {
  const q = new URL(request.url).searchParams.get("q")?.trim();
  if (!q) {
    return Response.json(null);
  }

  // 1. Resolve the name to a domain with the Search API.
  const search = await fetch(`${BASE}/search?q=${encodeURIComponent(q)}`, {
    headers,
  });
  const [match] = search.ok ? await search.json() : [];
  if (!match) {
    return Response.json(null);
  }

  // 2. Enrich the domain with the Describe API.
  const res = await fetch(`${BASE}/describe/${match.domain}`, { headers });

  // Describe returns 202 while it indexes a domain for the first time. Pass that
  // signal through so the client can retry instead of caching empty brand data.
  if (res.status === 202) {
    return Response.json(
      { name: match.name, domain: match.domain, pending: true },
      { status: 202 }
    );
  }

  const brand = res.ok ? await res.json() : null;

  // A known domain can still come back sparse, so return the name and domain
  // either way and let the logo stand in until the rest fills in.
  return Response.json({
    name: match.name,
    domain: match.domain,
    description: brand?.description || null,
    colors: brand?.colors ?? [],
    socials: brand?.socials ?? {},
  });
}
The browser never sees your secret key. It calls /api/enrich-company?q=... and builds the logo URL from the returned domain with your publishable key. On the card, render the returned description and the social links in socials. Only the networks present in the response render, so an employer with two profiles shows two icons and one with five shows five. Employers with no brand data on file still show their logo and name. The Describe API returns both fields, keyed off the domain the Search API resolved.

Next steps

Logo API

Browse every image URL parameter: size, format, theme, greyscale, and fallbacks.

Integration logos

Build the tile-based version for app marketplaces.