Brandfetch Logo Integration#
Sure uses the Brandfetch CDN to serve logos for accounts (financial institutions), merchants, and securities. All logo generation is gated on Setting.brand_fetch_client_id — if this setting is absent, no Brandfetch URLs are produced and the UI falls back to color avatars or provider-supplied logos .
The full setup guide is in docs/hosting/logos.md.
URL Structure#
Every Brandfetch CDN URL follows the pattern:
https://cdn.brandfetch.io/{identifier}/icon/fallback/lettermark/w/{size}/h/{size}?c={client_id}
The fallback/lettermark segment means Brandfetch will return a generated lettermark if it has no real logo for the identifier — a silent failure mode rather than a broken image .
The {size} parameter is controlled by Setting.brand_fetch_logo_size, which returns 40 (standard) or 120 (high-res) depending on Setting.brand_fetch_high_res_logos . Because URLs are stored with whatever size was current at write time, Setting.transform_brand_fetch_url rewrites the w/…/h/… portion at render time to reflect the live setting .
Per-Entity Behavior#
Accounts (Financial Institutions)#
Account#logo_url builds the Brandfetch URL directly from institution_domain. Priority order :
- Brandfetch CDN — used when
institution_domainis set andbrand_fetch_client_idis configured - Provider logo URL (e.g., Plaid's CDN)
- Attached file (
ActiveStorage)
institution_domain is populated from Plaid integration data — accounts without a linked Plaid connection won't have it .
Merchants#
Two merchant subtypes produce Brandfetch URLs differently:
ProviderMerchant — logo generated during AI-powered merchant enrichment. ProviderMerchant::Enhancer calls the configured LLM to discover a business_url for each unenhanced merchant, then build_logo_url constructs the CDN URL. It can also be (re-)generated on demand via generate_logo_url_from_website!, which is useful after a website URL is manually corrected.
FamilyMerchant — logo is auto-generated via a before_save callback whenever website_url is set or changed. The trigger condition is :
website_urlchanged, orwebsite_urlis present butlogo_urlis blank
If website_url is cleared, logo_url is set to nil .
Both subtypes strip www. from the domain before constructing the URL .
Securities#
Security uses a brandfetch_icon_url helper that resolves the identifier as:
- Domain extracted from
website_url(if present) - Ticker symbol as a fallback
For crypto securities, a separate brandfetch_crypto_url class method uses the path https://cdn.brandfetch.io/crypto/{base_asset}/… (e.g., crypto/BTC/…).
Security#display_logo_url applies this priority logic :
- Crypto: Brandfetch crypto URL → stored
logo_url - With
website_url: Brandfetch domain URL → storedlogo_url - Without
website_url: storedlogo_url→ Brandfetch ticker lettermark (provider logos win over ticker-only placeholders)
Logo regeneration fires on before_save when logo_url is blank, or when a stored Brandfetch URL exists and website_url or ticker changes. Cash securities are always skipped .
Note: Ticker-based matching for securities cannot specify the exchange. US exchanges are prioritized, so non-US securities may get the wrong logo .
Configuration#
| Setting | Env var | Default |
|---|---|---|
brand_fetch_client_id | BRAND_FETCH_CLIENT_ID | (none) |
brand_fetch_high_res_logos | BRAND_FETCH_HIGH_RES_LOGOS | false (40 px) |
The client ID can be entered in the UI under Settings → Self-Hosting or set via the environment variable for the web and worker services . The env var is declared in .env.example.
Limitations & Known Gaps#
- Local/obscure merchants: Brandfetch only covers well-known brands. Very local vendors (e.g., a workplace vending machine) return a lettermark placeholder or nothing useful. A feature request for manual logo uploads exists but is not yet implemented .
- Non-US securities: Exchange-unaware ticker lookup may surface the wrong logo for non-US instruments .
- Lettermark fallback is silent: The CDN returns a generated lettermark rather than an HTTP error for unrecognized identifiers, so missing logos are indistinguishable from real ones without visual inspection.
- No rate-limit handling: The app does not implement any explicit Brandfetch API rate-limit backoff — the CDN is queried at URL-build time; the URL itself is stored and reused.