How Your Logo Gets Stored When You Upload It
In this article
Your business logo shows up on every invoice you generate, which means it needs to still be there months or years later, even if the place you originally got the image from goes offline, changes its URL structure, or starts blocking hotlinked requests. That reliability requirement is why logo storage works the way it does: if you point your business profile at a remote image URL, the server doesn't just save that URL — it fetches the image itself and converts it into something self-contained.
Two Different Starting Points, One Stored Result
There are really two ways a logo value can arrive at the server. If you upload a file directly through the logo picker, the browser typically already encodes it as a data URL — the image bytes themselves, base64-encoded and embedded directly in a string — before it's ever sent. If instead you paste in a link to an image hosted somewhere else, that's a plain http:// or https:// URL, which is a reference to an image rather than the image itself. The server treats these two cases differently: anything that's already a data URL is left untouched, while anything recognized as a remote HTTP or HTTPS URL gets fetched and converted before being saved.
What Happens When You Point It at a Remote URL
When the value being saved is a remote URL, the server makes its own HTTP request to fetch it — sending an Accept header listing common image formats and a distinct user-agent identifying the request as coming from the invoicing platform's own fetcher, not a browser. If that request fails outright, or the response's content type doesn't start with image/, the save is rejected with a message telling you the logo URL couldn't be loaded and suggesting you upload the file directly or use a URL that's actually reachable. This check exists specifically to stop someone from pointing the logo field at, say, an HTML page or a redirect chain that never resolves to real image bytes.
The 2MB Cap, Checked Twice
Fetched images are capped at 2MB, and that limit is enforced in two places rather than one. First, if the response's content-length header reports a size over the cap, the request is rejected immediately without downloading the body at all. But because a server isn't obligated to send an honest content-length header, the actual downloaded byte count is checked again after the full image has been read into memory, and rejected at that point too if it turns out to exceed the cap despite what the header claimed. Both checks throw the same "image is too large" error; the difference is purely about not wasting time and bandwidth downloading an oversized file when the header alone already gives it away.
Why Convert to a Data URL Instead of Just Storing the Link
Once a fetch succeeds and passes both size checks, the image bytes are base64-encoded and combined with the response's content type into a data URL, and that data URL — not the original remote link — is what gets saved as your logo. This is the part that actually solves the reliability problem: once the conversion happens, your stored logo no longer depends on the original URL staying online, unchanged, and reachable. If the site you originally linked to later goes down, changes its image paths, or starts blocking requests from outside browsers, your invoices are unaffected, because the platform isn't fetching that URL anymore — it fetched it exactly once, at save time, and has kept its own self-contained copy ever since.
The Trade-Off You're Accepting
The cost of this approach is that your logo is frozen at whatever it looked like the moment you saved it. If you update the image at the original URL later — a redesign, a higher-resolution version — that change won't propagate to your invoice logo automatically, because the platform is no longer looking at that URL at all after the initial conversion. Updating your logo going forward means saving your business profile again with the new image, the same way you set it up the first time, rather than editing the file at the original address and expecting it to flow through.
Related Articles
How an Invoice Is Stored: One Row per Line Item, Plus a Full Copy
A saved invoice is stored as one database row per line item, linked by a shared group ID, with a full copy of the invoice on every row. Saving an edit replaces all of those rows in one transaction.
Why Sign-In Attempts Are Rate Limited
Sign-in, sign-up, two-factor checks, and verification resends share a budget of 10 attempts per hour per IP address, which is much stricter than the limit on ordinary app requests. Here's why the numbers differ.
How Sign-Up and Email Verification Actually Work
Creating an account sets up a personal workspace in the same database transaction, the verification link lasts 24 hours, and clicking an expired one quietly sends you a fresh link instead of leaving you stuck.
Why You Get Logged Out After an Hour
Every session token expires in exactly one hour, and there's no refresh mechanism behind the scenes extending it quietly — plus the one deliberate exception carved out for two-factor logins.
How Client Portal Links Expire
A portal link defaults to 30 days, checks expiry on every request rather than up front, and matches a client's documents by an exact, case-sensitive-safe comparison — not a database foreign key.
How Invoice View Tracking Actually Works
Two separate mechanisms record when a client opens your invoice — a running counter and a detailed event log — and a password-protected share link doesn't count as viewed until the password is entered.