Mei runs operations at a mid-sized equipment manufacturer. Fourteen months after her system went live, she asked me a question I did not have a good answer for.
"The contract is attached to the order," she said. "The order is visible to the auditor. So why can't the auditor open the contract?"
I opened the record with her. The field was there. The filename was there. The link was there. She could see, with complete certainty, that a signed contract existed for that order — and she could not open it. Both of those facts were correct according to the system. Only one of them was useful.
That was the day I stopped thinking of attachments as a field type with a paperclip icon.
Every platform ships attachments in week three
Attachments are the easiest feature to demo and the hardest feature to own. Someone uploads a PDF next to a record, a thumbnail appears, and everyone concludes that the platform handles files. It does handle files. It handles the upload. Everything after the upload belongs to a different problem, in a different system, with different guarantees — and almost nobody builds that part on purpose.
A normal field is a value in a row. An attachment is a value in a row that points at bytes living somewhere else. That somewhere else is a second database: an object store, a shared file server, a bucket with its own lifecycle rules, its own backup schedule, its own access model, and its own idea of what "delete" means.
Your data model is only as honest as the weakest link between those two systems.
The address is part of the identity, and the URL is the permission
Here is a detail most people learn the hard way. In a low-code platform, a file's identity is usually bound to where it sits in the metadata tree — application, then module, then field, then file. The path is the address. Which means renaming a module, moving a field into a different section, or reorganizing a form can change where three-year-old files are supposed to live. The row still points somewhere. Whether that somewhere is still reachable depends on whether every rename was paired with a migration that nobody thought to request.
Now add the second half, and this is the one that keeps me up.
Attachment access is typically granted by a token embedded in the file URL itself. This is not a design mistake. It is how you let a browser render a preview, or a rich-text field show an inline image, without forcing the object store to understand your permission model. But the consequence is that the URL becomes a bearer credential. Whoever holds the string, opens the file.
So your permission system decides who may see the field, and a string of characters decides who may see the bytes beneath it. Two decisions, made in two places, at two times, by two pieces of code that have never spoken to each other. We spent an entire article's worth of energy on field-level security and then handed the actual confidential document to anyone with a copy-paste habit.
Deleting a record does not delete a file
Ask a platform team what happens to the bytes when a record is deleted, and you will get one of three answers: nothing, eventually, or we were hoping you would not ask.
The row disappears instantly. The file stays — because something might still reference it, because garbage collection is risky, because storage is cheap and correctness is not. So the customer's real data footprint, the thing their security questionnaire asks about and the thing a deletion request is actually about, is not the rows. It is the orphaned bytes that no record mentions anymore.
Retention is the mirror image of the same problem. Keep every version of every file forever and you cannot honor a retention policy. Hard-delete and you cannot honor an audit. Both requirements arrive in the same procurement document, from the same customer, usually on facing pages.
Three copies of everything, and a bill nobody reads
Attachments do not stay one file. They get previews and thumbnails, sometimes a converted PDF, sometimes an extracted text layer for search. Each of those is a derived copy, and each is its own leakage surface — a cached thumbnail of a confidential contract, served from a CDN edge you do not control, is still a copy of the contract.
Mobile uploads on flaky connections multiply this further. A user on a factory floor with two bars of signal retries an upload three times and you get three files, two of them truncated. Nothing errors. The record simply has a document that opens to a blank page.
And underneath all of it sits a storage bill that no one modeled, because "unlimited attachments" was a line in a sales deck. Per-tenant storage quotas only become visible when an invoice arrives, and by then the conversation is no longer technical.
Restore is where the lie gets exposed
Backups are the test that finds all of this at once.
You restore the database. Every row comes back. Every attachment field renders. Some files resolve, because they were still in the bucket. Some do not, because a lifecycle rule quietly cleaned them up six months ago, or because the bucket snapshot runs on a different schedule and you restored to a point in time four hours away from the rows.
The database and the object store are two independent histories that you have been treating as one system in your head. Your disaster recovery plan says "restore the platform." Nobody wrote down that the platform is two things.
And now an agent reads your attachments
The newest complication is that attachments stopped being passive. Agents now parse invoices, read contracts, pull line items out of a scanned delivery note. Which means the file layer just became an input pipe into your business logic.
A PDF is not a value you can validate. It is untrusted content in a container your users trust, and it can carry instructions as easily as it carries an invoice number. Every attachment is simultaneously a document a human will read and a payload a machine will execute against. The security review that covered "users can upload files" did not cover that, because when that review was written, nothing downstream was reading them.
The uncomfortable conclusion
We sell low-code platforms as systems that model your business. That claim holds right up to the boundary where your data stops being rows and starts being bytes.
Attachments are the exact point where the platform stops being one system and quietly becomes two — with a seam nobody owns, a permission model living in two places, a deletion story with no single answer, and a restore procedure that assumes the halves were in sync.
Mei's auditor never saw an error. That was the worst part. The system told her the contract was there, and the system was right, and she still could not do her job. A silent partial truth is harder to fix than a failure, because nobody files a ticket for a blank preview pane that looks exactly like a missing file.
An attachment field is a second database you inherited without being told. Your data model is not finished until you can answer, out loud, who deletes those bytes, who is allowed to read them, and where they go when you restore.
If you cannot answer those three questions, you do not have an attachment feature.
You have a filing cabinet with no locks and no inventory.
Top comments (0)