Contents
A customer rebrands. New logo, new letterhead, slightly different blue. Somebody has to go through fifty-two document templates and update the image in each one.
That was the position until this summer, and the reason was a decision made years earlier that looked entirely sensible at the time.
How the logo used to get into the document
Templates are HTML. When a document was generated, any image referenced in the template was fetched from object storage, base64-encoded, and inlined into the markup before rendering.
It works. It's also the reason for the fifty-two edits, because an image handled that way has no identity beyond the string sitting in each template. There's no list of what your brand assets are, no way to change one in a single place, and no way to see what a template will look like without generating a document to find out.
It's slow, too. Every generation pays for a fetch and an encode of bytes that never change.
So assets moved to being served from a route, referenced by URL, with an upload and management panel inside the template editor. Upload a letterhead once, use it in every template, replace it in one place. The editor lists what exists, so you pick from your actual assets instead of remembering a filename.
Which raises the question that made this a fortnight of work rather than an afternoon.
Opening an anonymous door into a document system
For a template to reference an asset by URL, that URL has to be fetchable at generation time without a session. That means an unauthenticated route.
SwiftCase holds client documents. Medical reports, engineer's assessments, settlement letters, correspondence about people's claims. Adding an anonymous endpoint to that system is the sort of change that deserves more than a glance.
The route exists, and a file is served through it only when every one of five conditions holds:
- The file has not been deleted
- The file is not attached to a case
- The file belongs to the main organisation, checked by comparing ownership against the main organisation's own identifier
- The file carries the public-asset tag, applied deliberately
- The file's extension is on the allow-list
Any one failing means nothing is served.
The third and fourth are the ones doing the real work. Case files are excluded structurally, so no document belonging to a client can ever be reachable this way regardless of how it is tagged. And publication is opt-in through a tag applied by an administrator, so a file has to be deliberately marked before it becomes reachable by anyone.
The allow-list is narrow: PNG, JPEG, GIF and WebP for images, WOFF, WOFF2 and TTF for fonts, plus CSS and JavaScript. Each extension maps to an explicit content type, so nothing is served with a guessed one.
Ordering matters here. The checks are arranged so the cheap structural ones run first and the deny is the default at every step. The failure mode of this code is refusing to serve something it should have served, which shows up immediately as a missing logo and gets fixed in a minute.
Fonts and stylesheets, which is where it gets useful
Once assets are served properly, a template stops being limited to images.
A brand typeface can be uploaded as WOFF2 and referenced from the template. A shared stylesheet can carry the house rules for headings, tables and spacing, so fifty-two templates stop each carrying their own slightly divergent copy of the same CSS. That's the change that makes a rebrand a one-file job.
JavaScript came later and is the one to be careful with. It's genuinely useful for a small class of documents that render conditionally, and it's also an anonymous endpoint serving executable content, which is why the same five conditions apply and why an administrator has to tag it on purpose.
What a rebrand touches now, and what it doesn't
Replace the file behind the asset. Every template referencing it picks up the new version on the next generation.
Documents already produced keep the old logo, which is correct behaviour and worth saying out loud. A settlement letter sent in March was sent on March's letterhead. If somebody requests that document during a complaint two years later, it should look like the document that was sent, and a system that retroactively rebrands its own archive is a system with an evidential problem.
So the generated output still contains the rendered image. The asset route is used at generation time, and after that the document stands on its own.
The caching detail
The list of available assets is cached for five minutes.
That number came from watching the two failure modes. Uncached, the template editor queries for the asset list on every render, which is a lot of identical work for something that changes when somebody uploads a file. Cached for too long, an administrator uploads a logo, does not see it, uploads it again, and now you have two.
Five minutes is long enough to absorb the editor traffic and short enough that the second upload has not happened yet.
The general shape
The version of this feature that gets described in a changelog is "upload and manage assets from the template editor", which sounds like a panel with a file input.
The work was deciding what an anonymous route into a document system is allowed to serve, then writing that decision as a series of checks that fail closed and reading them back to ask what a determined person could do with the endpoint. Everything else was a panel with a file input.
That ratio is normal. The feature is small and the boundary it crosses is not, and the boundary is where the time goes.
Further reading:
- Document generation: templates, merge fields and batch output
- Documents and email: creating templates and inserting task information tags
- The complete guide to document generation: the longer walkthrough
- Security: how access is gated across the platform
