Adding logos to customers with ease!

In this video, I build a small tool to populate the customer pictures with nice-looking logos. Check out the video:

https://youtu.be/xLJnF38UAe4


Ever noticed how the customer list in Business Central looks bland and lifeless without photos or logos? The demo data from Cronus comes with nice studio photos of happy-looking models, but in real-world implementations, the image field is almost always empty — making the tiles view and card pages waste a lot of screen real estate. In this video, Erik shows a clever trick: using the free Logo API from Clearbit to automatically download company logos based on their email or homepage domain and populate the customer image field in bulk.

The Problem: Empty Customer Photos

If you look at the Cronus demo data, the five default customers have nice photos assigned to them. But any customers you create yourself? They’re blank. The tiles list view becomes pointless, and the customer card wastes space showing an empty image placeholder. While you could manually upload a photo for each customer, Erik has never met a customer who actually does that.

The Secret Ingredient: Clearbit’s Logo API

Erik discovered a free service at logo.clearbit.com that returns a company’s logo when you pass it a domain name. For example, navigating to https://logo.clearbit.com/canon.com returns Canon’s logo as a PNG image. It even works with the www. prefix. This simple API becomes the foundation for automatically populating customer logos.

Building the Solution Step by Step

The approach breaks down into three steps:

  1. Figure out the domain — from the customer’s homepage or email address
  2. Download the logo — using an HTTP client to call the Clearbit API
  3. Save the logo as the customer’s picture — writing it into the Image field

Step 1: Extracting the Domain

Most customers will have at least an email address or homepage filled in. If the homepage field has a value, we use that directly as the domain. If not, we extract the domain from the email address. Since email fields can contain multiple addresses separated by semicolons (like invoicing@nvidia.com;support@nvidia.com), we first split on the semicolon to get the first email, then split on @ to extract just the domain portion.

Step 2: Downloading the Logo via HTTP

With the domain in hand, we construct a URL like https://logo.clearbit.com/{domain} and use Business Central’s HTTP client to send a GET request. One important gotcha Erik highlights: when using an HTTP client inside a loop, you need to clear the request and response objects on each iteration. Otherwise you’ll get a “request operation cannot be performed in this context” error.

The distinction between the HTTP send failing versus getting an unsuccessful HTTP response is also worth noting. If Client.Send() returns false, it means the request never reached the HTTP protocol level — something is wrong with the URL or the server connection. An unsuccessful response (like a 404) means the request was sent and received, but the server couldn’t fulfill it.

Step 3: Saving the Logo to the Customer Record

Since the logo is binary data, we work with streams. We read the response content into an InStream, then use the customer’s Image field to import from that stream, specifying the MIME type (image/png) and a filename. Finally, we call Customer.Modify() to persist the change.

The Complete Code

pageextension 50100 "Customer List Ext" extends "Customer List"
{
    actions
    {
        addfirst(processing)
        {
            action(AddLogos)
            {
                Caption = 'Add Logos';
                ApplicationArea = All;

                trigger OnAction()
                begin
                    AddLogoToCustomers();
                end;
            }
        }
    }

    local procedure AddLogoToCustomers()
    var
        Customer: Record Customer;
        Client: HttpClient;
        Request: HttpRequestMessage;
        Response: HttpResponseMessage;
        InStr: InStream;
        Domain: Text;
        Emails: List of [Text];
    begin
        Customer.SetFilter("No.", 'C*');
        if Customer.FindSet() then
            repeat
                // Skip customers that already have an image
                if not Customer.Image.HasValue() then begin
                    Domain := '';

                    // 1. Figure out domain
                    if Customer."Home Page" <> '' then
                        Domain := Customer."Home Page"
                    else begin
                        Emails := Customer."E-Mail".Split(';');
                        if Emails.Count() > 0 then
                            Domain := Emails.Get(1).Split('@').Get(2);
                    end;

                    if Domain <> '' then begin
                        // 2. Download the logo
                        Clear(Request);
                        Clear(Response);
                        Request.SetRequestUri('https://logo.clearbit.com/' + Domain);
                        Request.Method('GET');

                        if Client.Send(Request, Response) then
                            if Response.IsSuccessStatusCode() then begin
                                // 3. Save logo as picture
                                Response.Content.ReadAs(InStr);
                                Customer.Image.ImportStream(InStr, 'logo.png', 'image/png');
                                Customer.Modify();
                            end;
                    end;
                end;
            until Customer.Next() = 0;
    end;
}

Key Technical Details

InStream vs OutStream

An InStream is something you read data from; an OutStream is something you write data to. When you want to put something into a picture field, you might expect to use an OutStream, but here we use ImportStream which takes an InStream — the stream we’re reading the HTTP response content from. Erik acknowledges this can be confusing and references an older video on his channel about stream directions.

Clearing HTTP Objects in a Loop

When reusing HTTP client objects inside a loop, you must clear the request and response objects between iterations. The client itself doesn’t necessarily need to be cleared — in fact, you probably shouldn’t clear it, as it can be reused across requests.

Filtering for New Customers Only

The code includes a check: if not Customer.Image.HasValue(). This ensures we only download logos for customers that don’t already have an image, avoiding unnecessary API calls on subsequent runs.

The Results

After running the action, Erik’s customer list immediately showed logos for Apple, Microsoft, Nvidia, Walmart, BMW, Porsche, Canon, Roland, IBM, and ASUS — all automatically downloaded and assigned. The tiles view in particular looks dramatically better with actual company logos instead of blank placeholders.

Some logos work better than others in Business Central’s square cropping format. The Apple and Nvidia logos fit well, while Microsoft’s logo gets an odd circular crop. But overall, it’s a massive visual improvement over empty image fields.

Conclusion

With just a single procedure and a free API, you can transform the look of your customer list from a wall of blank placeholders into a visually rich, logo-filled experience. The approach is straightforward: extract a domain from existing customer data, call the Clearbit Logo API, and stream the result into the customer’s image field. This same pattern could easily be extended to vendors or other entities. Erik even floated the idea of packaging this as a free AppSource app — a small but genuinely useful quality-of-life improvement for any Business Central environment.