A user clicks "Delete Account." Your database says the row is gone. Three weeks later, a support ticket arrives: "Why can I still find my old resume.pdf in a Google search result?"
This happens more often than most engineering teams admit. File-processing apps have a deletion problem that regular CRUD apps don't: the data isn't just a table row. It's a file sitting in object storage, a thumbnail in a CDN cache, a backup snapshot from last Tuesday, and maybe a copy in a processing queue that hasn't run yet. Deleting the record is the easy 10%. Deleting the actual data is the hard 90%.
If you're building anything that touches uploaded files, whether it's a PDF converter, an image editor, a document scanner, or a media pipeline, here's what proper deletion actually requires.
Why "DELETE FROM users WHERE id = ?" Isn't Deletion
Most developers learn database deletion before they learn systems thinking. So the instinct is to treat account deletion as a database problem. But in a file-processing app, the database is just an index. It points to where things live. Deleting the pointer doesn't delete the destination.
Consider a typical upload-process-download flow:
- User uploads a file to object storage (S3, GCS, or similar)
- Your app processes it and stores a result, maybe in a different bucket
- A thumbnail or preview gets generated and cached at the edge
- Metadata about the operation lands in your database and possibly your logs
- A backup job later that night copies everything, including the file, into a snapshot Now the user asks you to delete their data. If you only touch step 4, you've deleted the map, not the territory. The file is still sitting in storage, still reachable if someone has the direct URL, and still present in that night's backup for as long as your retention policy holds it.
The Four Places Data Actually Lives
Before writing any deletion logic, map out where a single file's data physically exists. In most systems, it's some combination of:
Primary storage. The actual file object, wherever you store it after upload.
Derived artifacts. Thumbnails, converted formats, compressed versions, OCR text extracts. If your app converts a file from one format to another, you likely have both the original and the output sitting somewhere.
Cache and CDN layers. If files are served through a CDN, deleting the origin file doesn't automatically purge edge caches. Someone with a cached URL might retrieve it for hours or days after "deletion."
Backups and logs. This is the one teams forget. Your nightly backup rotation might retain deleted files for 30, 60, or 90 days depending on your retention window. Application logs sometimes contain file paths, filenames, or even payload snippets from debugging.
A deletion request that only clears the first item on this list isn't deletion. It's an illusion of deletion, and it becomes a real liability under regulations like GDPR's right to erasure or CCPA's deletion rights, both of which expect data to actually be gone within a defined timeframe, not just hidden from the UI.
Designing a Deletion Flow That Actually Works
Treat deletion as a job, not a click handler. Don't try to synchronously delete everything the moment a user clicks a button. Object storage calls can fail, CDN purges can be rate-limited, and backup systems often can't be touched on demand. Queue the deletion as a background job with retries, and give the user immediate confirmation that the request was received rather than promising instant completion.
Build a deletion manifest per file, not per user. When a file is uploaded, track every artifact it spawns: the original, the converted output, the thumbnail, the cache key. Store this as a small structured record tied to the file's ID. When deletion runs, you're not guessing what might exist, you're working off a checklist.
Separate soft delete from hard delete. A soft delete (a flag that hides the file from the user's dashboard) is fine as a first step and even useful for accidental deletion recovery. But it must have a hard deadline. If your soft delete window is 7 days, the hard delete job must actually run on day 7, permanently removing the underlying object, not just flipping another flag.
Purge caches explicitly. If you use a CDN, issue an invalidation request as part of the deletion job. Don't assume TTL expiry is good enough, especially for sensitive documents like IDs, contracts, or financial statements.
Set backup retention with deletion in mind. You generally can't reach into last week's backup and surgically remove one file. The realistic approach is to keep backup retention short and clearly documented, and to make sure your privacy policy accurately reflects that deleted files may persist in backups for up to that retention period. Honesty here matters more than a technically perfect solution.
Log the deletion, not the deleted content. Keep an audit trail that says "file X was deleted on this date, triggered by this user action," but make sure your logs never captured the file's contents or sensitive metadata in the first place. If they already did, redact retroactively.
Where This Gets Genuinely Difficult
Multi-tenant systems and processing pipelines add complexity. If a file passes through a third-party API for conversion or OCR, you need to confirm what that provider does with the data after processing, and whether they offer their own deletion endpoint. A deletion flow that stops at your own infrastructure but ignores a downstream vendor is incomplete.
This is actually one of the reasons PDF Conveterhas some conversion features around browser-based processing rather than uploading files to a server at all. For most of its conversion and editing tools, the file is processed locally in the user's browser and never persisted server-side, and files that do pass through are auto-deleted rather than retained. It sidesteps the entire "did we really delete it everywhere" problem for a large share of common file operations, because there's no long-term server copy to hunt down in the first place. It's a useful pattern to study if you're deciding between server-side and client-side processing for your own app: sometimes the simplest way to guarantee deletion is to not create a permanent copy at all.
A Practical Checklist
Before shipping a "Delete My Data" feature, confirm:
- Every file has a documented list of derived artifacts and where they live
- Deletion is a queued job with retry logic, not a synchronous call
- CDN cache purging is part of the deletion flow, not an afterthought
- Backup retention windows are documented and disclosed to users
- Downstream third-party processors have their own deletion path accounted for
- Logs don't retain file content, only metadata about the deletion event itself Data deletion isn't a feature you bolt on at the end. It's a property of how you architect file handling from the start. The apps that get this right treat every file upload as a liability with a lifecycle, not just an asset to serve. Building that mindset early saves a lot of uncomfortable support tickets later.
Top comments (0)