> ## Documentation Index
> Fetch the complete documentation index at: https://www.logo.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Import brand assets from a domain

> Skip the logo-upload step. One domain fills an asset tray with the icon, wide brandmark, social banners, and brand colors, ready to drop into a canvas.

export const BrandAssetsDemo = () => {
  const TOKEN = "live_6a1a28fd-6420-4492-aeb0-b297461d9de2";
  const BRANDS = [{
    name: "Spotify",
    slug: "spotify",
    domain: "spotify.com",
    colors: ["#00d84d", "#83eca8", "#ffffff"]
  }, {
    name: "Stripe",
    slug: "stripe",
    domain: "stripe.com",
    colors: ["#533afd", "#000001", "#ffffff"]
  }, {
    name: "Figma",
    slug: "figma",
    domain: "figma.com",
    colors: ["#12c6bf", "#dc5e80", "#000000"]
  }, {
    name: "Canva",
    slug: "canva",
    domain: "canva.com",
    colors: ["#5947ea", "#11aed1", "#eff7f9"]
  }];
  const [selected, setSelected] = useState(0);
  const [loadedBanners, setLoadedBanners] = useState({});
  const brand = BRANDS[selected];
  const [assetBase] = useState(() => typeof window !== "undefined" && window.location.pathname.startsWith("/docs") ? "/docs" : "");
  const markBannerLoaded = slug => setLoadedBanners(m => m[slug] ? m : {
    ...m,
    [slug]: true
  });
  const iconSrc = `https://img.logo.dev/${brand.domain}?token=${TOKEN}&size=40&retina=true&format=webp`;
  const brandmarkSrc = `${assetBase}/images/use-cases/brand-assets/${brand.slug}-brandmark.webp`;
  const bannerSrc = `${assetBase}/images/use-cases/brand-assets/${brand.slug}-banner.webp`;
  const bannerLoaded = loadedBanners[brand.slug];
  return <div className="not-prose my-8 rounded-2xl border border-zinc-950/10 bg-gradient-to-br from-zinc-50 to-white p-6 shadow-sm sm:p-8 dark:border-white/10 dark:from-zinc-900 dark:to-zinc-950">
      <div className="mx-auto max-w-md space-y-4">
        <div className="flex flex-wrap gap-2">
          {BRANDS.map((b, i) => <button aria-pressed={i === selected} className={`relative rounded-full border px-3.5 py-2 text-xs font-medium transition-[background-color,scale] duration-150 before:absolute before:-inset-1 before:content-[''] focus:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950/20 motion-safe:active:scale-[0.96] dark:focus-visible:ring-white/20 ${i === selected ? "border-zinc-950/20 bg-zinc-100 text-zinc-950 dark:border-white/25 dark:bg-zinc-800 dark:text-white" : "border-zinc-950/10 bg-white text-zinc-600 hover:bg-zinc-50 dark:border-white/15 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800"}`} key={b.slug} onClick={() => setSelected(i)} type="button">
              {b.name}
            </button>)}
        </div>
        <div className="rounded-xl border border-zinc-950/10 bg-white p-4 dark:border-white/10 dark:bg-zinc-900">
          <div className="mb-3 text-xs font-medium text-zinc-400">
            {brand.name} brand assets
          </div>
          <div className="grid grid-cols-2 gap-3">
            <div className="flex h-20 items-center justify-center rounded-lg bg-white ring-1 ring-zinc-950/5">
              <img alt={`${brand.name} icon`} className="h-10 w-10 rounded-lg object-contain" height="40" key={`${brand.slug}-icon`} src={iconSrc} width="40" />
            </div>
            <div className="flex h-20 items-center justify-center rounded-lg bg-white px-4 ring-1 ring-zinc-950/5">
              <img alt={`${brand.name} brandmark`} className="max-h-6 w-auto max-w-full object-contain" key={brandmarkSrc} src={brandmarkSrc} style={{
    height: 24
  }} />
            </div>
          </div>
          <div className="relative mt-3 aspect-[1.91/1] overflow-hidden rounded-lg bg-zinc-100 dark:bg-zinc-800">
            {!bannerLoaded && <span aria-hidden="true" className="absolute inset-0 animate-pulse bg-zinc-200 motion-reduce:animate-none dark:bg-zinc-700" />}
            <img alt={`${brand.name} banner`} className={`h-full w-full object-cover transition-opacity duration-300 motion-reduce:transition-none ${bannerLoaded ? "opacity-100" : "opacity-0"}`} key={bannerSrc} onLoad={() => markBannerLoaded(brand.slug)} ref={el => {
    if (el?.complete && el.naturalWidth > 0) {
      markBannerLoaded(brand.slug);
    }
  }} src={bannerSrc} />
          </div>
          <div className="mt-3 flex gap-2">
            {brand.colors.map(hex => <div className="flex-1" key={hex}>
                <div className="h-8 rounded-md ring-1 ring-inset ring-zinc-950/10" style={{
    backgroundColor: hex
  }} />
                <div className="mt-1 text-center font-mono text-[10px] text-zinc-400">
                  {hex}
                </div>
              </div>)}
          </div>
        </div>
      </div>
    </div>;
};

Slide tools, design apps, and website builders all need the user's brand, and the upload step asks for files most people don't have on hand. An import that fills itself from a domain removes it.

A user types their domain, and a tray fills with the square icon, the wide brandmark, social banners, and brand colors. The [Brand API](/docs/brand/introduction) returns the whole kit in one request, ready to drop into a canvas.

## Demo

Pick a brand to fill the asset tray:

<BrandAssetsDemo />

The brandmark and banner above were fetched once and committed, because the Brand API takes a secret key and its asset URLs can rotate. The square icon loads live from `img.logo.dev`.

## Build it with AI

<Prompt description="Want this brand import in your app? This prompt gives your AI coding tool everything it needs to build it in your framework." actions={["copy", "cursor"]}>
  Build a brand asset import: a user types a domain and a tray fills with that brand's assets.

  * Add a small server endpoint that proxies the Logo.dev Brand API: GET [https://api.logo.dev/brand/:domain](https://api.logo.dev/brand/:domain) with header Authorization: Bearer LOGO\_DEV\_SECRET\_KEY. Return the name, the brandmark URL, the social banner URLs, and the palette hex values.
  * Filter out blank banner entries; some social\_banners URLs have no asset path.
  * In the client, render a domain input with an Import button, a loading skeleton, and the loaded tray: square icon, wide brandmark on a white well, the first banner, and a row of color swatches.
  * The square icon is a plain image URL with a publishable key: [https://img.logo.dev/:domain?token=LOGO\_DEV\_PUBLISHABLE\_KEY\&size=40\&retina=true\&format=webp](https://img.logo.dev/:domain?token=LOGO_DEV_PUBLISHABLE_KEY\&size=40\&retina=true\&format=webp)
  * Brandmark and banner URLs accept size, retina=true, and format=webp parameters. Add theme=light to the brandmark on white wells so light wordmarks stay visible.
  * When the kit lookup fails, fall back to the square icon, which degrades to a generated monogram, so the user always gets at least one usable asset.
  * The secret key stays on the server; the publishable key is safe in client code.
  * Reference implementation: [https://www.logo.dev/docs/use-cases/brand-assets](https://www.logo.dev/docs/use-cases/brand-assets)
</Prompt>

## Code

This example is in React, but you can get the same result in any frontend framework: the tray is a proxy endpoint, one fetch, and plain images. Add `api/brand-assets.ts` on the server, then render the tray as the Usage tab shows.

<CodeGroup>
  ```ts api/brand-assets.ts theme={null}
  type Brand = {
    name: string;
    brandmark: string | null;
    social_banners: string[];
    colors: { hex: string }[];
  };

  export async function GET(request: Request) {
    const domain = new URL(request.url).searchParams
      .get("domain")
      ?.trim()
      .toLowerCase();
    if (!domain) {
      return Response.json({ error: "domain required" }, { status: 400 });
    }

    const res = await fetch(`https://api.logo.dev/brand/${domain}`, {
      headers: { Authorization: `Bearer ${process.env.LOGO_DEV_SECRET_KEY}` },
    });
    if (!res.ok) {
      return Response.json({ error: "brand lookup failed" }, { status: 502 });
    }

    const brand = (await res.json()) as Brand;
    return Response.json({
      name: brand.name,
      brandmark: brand.brandmark,
      // Some banner entries are blank placeholders with no asset path.
      banners: (brand.social_banners ?? []).filter(
        (url) => new URL(url).pathname.length > 1
      ),
      colors: (brand.colors ?? []).map((c) => c.hex),
    });
  }
  ```

  ```tsx BrandAssetTray.tsx expandable theme={null}
  import { useState } from "react";

  type BrandAssets = {
    name: string;
    brandmark: string | null;
    banners: string[];
    colors: string[];
  };

  export function BrandAssetTray({ token }: { token: string }) {
    const [input, setInput] = useState("");
    const [domain, setDomain] = useState<string | null>(null);
    const [assets, setAssets] = useState<BrandAssets | null>(null);
    const [state, setState] = useState<"idle" | "loading" | "loaded" | "failed">(
      "idle"
    );

    const importBrand = async (event: React.FormEvent) => {
      event.preventDefault();
      const next = input.trim().toLowerCase();
      if (!next.includes(".")) {
        return;
      }
      setDomain(next);
      setState("loading");
      try {
        const res = await fetch(`/api/brand-assets?domain=${encodeURIComponent(next)}`);
        if (!res.ok) {
          throw new Error(`lookup failed: ${res.status}`);
        }
        setAssets(await res.json());
        setState("loaded");
      } catch {
        setAssets(null);
        setState("failed");
      }
    };

    return (
      <div className="max-w-md space-y-4">
        <form className="flex gap-2" onSubmit={importBrand}>
          <input
            className="w-full rounded-xl border border-zinc-950/10 bg-white px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-zinc-950/20"
            onChange={(e) => setInput(e.target.value)}
            placeholder="acme.com"
            type="text"
            value={input}
          />
          <button
            className="rounded-xl bg-zinc-900 px-4 text-sm font-semibold text-white"
            type="submit"
          >
            Import
          </button>
        </form>

        {state === "loading" && (
          <div
            aria-hidden="true"
            className="h-40 animate-pulse rounded-xl bg-zinc-100 motion-reduce:animate-none"
          />
        )}

        {state === "failed" && domain && (
          // The square icon still works for unknown brands: it falls back to a
          // monogram, so the user always gets at least one usable asset.
          <div className="flex items-center gap-3 rounded-xl border border-zinc-950/10 p-4">
            <img
              alt={`${domain} icon`}
              className="h-10 w-10 rounded-lg object-contain"
              height={40}
              src={`https://img.logo.dev/${domain}?token=${token}&size=40&retina=true&format=webp`}
              width={40}
            />
            <p className="text-sm text-zinc-500">
              No full brand kit for {domain} yet, but the logo is ready to use.
            </p>
          </div>
        )}

        {state === "loaded" && assets && domain && (
          <div className="space-y-3 rounded-xl border border-zinc-950/10 p-4">
            <p className="text-xs font-medium text-zinc-400">
              {assets.name} brand assets
            </p>
            <div className="grid grid-cols-2 gap-3">
              <div className="flex h-20 items-center justify-center rounded-lg bg-white ring-1 ring-zinc-950/5">
                <img
                  alt={`${assets.name} icon`}
                  className="h-10 w-10 rounded-lg object-contain"
                  height={40}
                  src={`https://img.logo.dev/${domain}?token=${token}&size=40&retina=true&format=webp`}
                  width={40}
                />
              </div>
              {assets.brandmark && (
                <div className="flex h-20 items-center justify-center rounded-lg bg-white px-4 ring-1 ring-zinc-950/5">
                  {/* The wells are white, so theme=light keeps light wordmarks
                      visible by inverting them. */}
                  <img
                    alt={`${assets.name} brandmark`}
                    className="h-6 w-auto max-w-full object-contain"
                    src={`${assets.brandmark}&size=160&retina=true&format=webp&theme=light`}
                  />
                </div>
              )}
            </div>
            {assets.banners[0] && (
              <img
                alt={`${assets.name} banner`}
                className="aspect-[1.91/1] w-full rounded-lg object-cover"
                loading="lazy"
                src={`${assets.banners[0]}&size=600&retina=true&format=webp`}
              />
            )}
            <div className="flex gap-2">
              {assets.colors.map((hex) => (
                <div
                  className="h-8 flex-1 rounded-md ring-1 ring-inset ring-zinc-950/10"
                  key={hex}
                  style={{ backgroundColor: hex }}
                  title={hex}
                />
              ))}
            </div>
          </div>
        )}
      </div>
    );
  }
  ```

  ```tsx Usage theme={null}
  import { BrandAssetTray } from "./BrandAssetTray";

  export function ImportPanel() {
    return <BrandAssetTray token="LOGO_DEV_PUBLISHABLE_KEY" />;
  }
  ```
</CodeGroup>

<Note>
  Your [publishable key](/docs/platform/api-keys) is built for client-side code, so
  you can ship it in the browser as-is. Your secret key powers the
  `api/brand-assets.ts` proxy and stays on the server.
</Note>

## How it works

* **One request returns the kit.** `GET /brand/:domain` from the [Brand API](/docs/brand/introduction) carries the name, brandmark, social banners, and color palette together, so the tray fills from a single fetch.
* **Failure still produces an asset.** Brandmark and banner URLs do not get the monogram fallback that square icon URLs have, which is why the tray falls back to `img.logo.dev/:domain` when the kit lookup fails. The user always gets at least one usable asset.
* **Every asset accepts image parameters.** Brandmark and banner URLs take the usual `size`, `format`, `retina`, and `theme` [image parameters](/docs/logo-images/introduction#parameters), so tray thumbnails and full-size inserts come from the same URLs. On white wells, `theme=light` keeps light wordmarks visible.
* **Banner lists need filtering.** Some `social_banners` entries are blank placeholder URLs with no asset path. The server route drops them so the tray never renders an empty image.

## Make it your own

* **Make assets insertable.** Wire each card to your editor's insert or drag-and-drop action. The asset URL is all a canvas needs.
* **Theme the document too.** Set the deck's accent from the same palette with the [brand colors](/docs/use-cases/brand-colors) pattern.
* **Cache the kit.** Brand kits change rarely, so cache the server response per domain instead of calling the [Brand API](/docs/brand/introduction) on every open.

## Next steps

<CardGroup cols={2}>
  <Card title="Brand API" icon="swatchbook" href="/docs/brand/introduction">
    Get the full response: logo, brandmark, banners, and colors.
  </Card>

  <Card title="Brand colors" icon="palette" href="/docs/use-cases/brand-colors">
    Theme generated content with the same palette.
  </Card>
</CardGroup>
