Soft deletes vs hard deletes: leer de echte afwegingen voor analytics, support, GDPR-achtige verwijdering en query-complexiteit, plus veilige restore-patronen.

Een verwijderknop kan twee heel verschillende dingen betekenen in een database.
Een hard delete verwijdert de rij. Daarna is het record weg, tenzij je backups, logs of replicaties hebt die het nog bevatten. Het is eenvoudig te begrijpen, maar definitief.
Een soft delete houdt de rij, maar markeert deze als verwijderd, meestal met een veld zoals deleted_at of is_deleted. De app behandelt gemarkeerde rijen dan als onzichtbaar. Je bewaart gerelateerde data, behoudt historie en kunt soms het record herstellen.
Deze keuze speelt vaker in dagelijkse werkzaamheden dan mensen verwachten. Het beïnvloedt hoe je vragen beantwoordt zoals: “Waarom daalde de omzet vorige maand?”, “Kun je mijn verwijderde project terughalen?”, of “We hebben een GDPR-verzoek — verwijderen we echt persoonsgegevens?” Het bepaalt ook wat “verwijderd” betekent in de UI. Gebruikers gaan er vaak vanuit dat ze het ongedaan kunnen maken, totdat dat niet meer kan.
Een praktische vuistregel:
Voorbeeld: een klant verwijdert een workspace en beseft later dat er facturen voor de boekhouding in stonden. Met soft delete kan support het herstellen (als je app gebouwd is om restores veilig af te handelen). Met hard delete sta je waarschijnlijk voor de keuze van backups, vertragingen of “het is niet mogelijk.”
Geen van beide benaderingen is per se “de beste.” De minst pijnlijke optie hangt af van wat je moet beschermen: gebruikersvertrouwen, rapportage-accuratesse of privacy-compliance.
Verwijderingkeuzes verschijnen snel in analytics. De dag dat je actieve gebruikers, conversie of omzet gaat bijhouden, wordt “verwijderd” geen eenvoudige staat meer maar een rapportagebeslissing.
Als je hard delete gebruikt, lijken veel metrics schoon omdat verwijderde records uit queries verdwijnen. Maar je verliest context: eerdere abonnementen, vorige teamgroottes of hoe een funnel er vorige maand uitzag. Een verwijderde klant kan historische grafieken laten verschuiven wanneer je rapporten opnieuw draait, en dat is beangstigend voor finance en growth reviews.
Als je soft delete gebruikt, bewaart je de historie, maar kun je per ongeluk cijfers opblazen. Een eenvoudige “COUNT users” kan mensen mee-tellen die zijn vertrokken. Een churn-grafiek kan dubbel tellen als je deleted_at in het ene rapport als churn beschouwt en in een ander negeert. Zelfs omzet kan rommelig worden als facturen blijven bestaan maar het account als verwijderd gemarkeerd is.
Wat vaak werkt is een consistente rapportage-aanpak kiezen en je daaraan houden:
Het belangrijkste is documentatie zodat analisten niet hoeven te raden. Schrijf op wat “actief” betekent, of soft-verwijderde gebruikers worden meegenomen, en hoe omzet wordt toegeschreven als een account later wordt verwijderd.
Concreet voorbeeld: een workspace is per ongeluk verwijderd en vervolgens hersteld. Als je dashboard workspaces telt zonder te filteren, zie je een plotselinge daling en herstel die in de echte gebruiksstatistieken niet heeft plaatsgevonden. Met snapshots blijft de historische grafiek stabiel terwijl productweergaven verwijderde workspaces kunnen verbergen.
De meeste supporttickets rondom verwijdering klinken hetzelfde: “Ik heb het per ongeluk verwijderd,” of “Waar is mijn record gebleven?” Je delete-strategie bepaalt of support binnen minuten kan antwoorden, of dat het enige eerlijke antwoord is: “Het is weg.”
Met soft deletes kun je meestal verifiëren wat er gebeurde en het ongedaan maken. Met hard deletes moet support vaak op backups vertrouwen (als je die hebt), en dat kan traag, incompleet of onmogelijk zijn voor één enkel item. Daarom is de keuze niet alleen een databasespec; het bepaalt hoe “behulpzaam” je product kan zijn nadat iets misging.
Als je echte support verwacht, voeg dan een paar velden toe die deletie-events uitleggen:
deleted_at (timestamp)deleted_by (user id of systeem)delete_reason (optioneel, korte tekst)deleted_from_ip of deleted_from_device (optioneel)restored_at en restored_by (als je herstel ondersteunt)Zelfs zonder een volledige activity log laten deze details support zien: wie het verwijderde, wanneer het gebeurde en of het een ongeluk of een geautomatiseerde cleanup was.
Hard deletes kunnen prima zijn voor tijdelijke data, maar voor gebruikersgerichte records veranderen ze wat support kan doen.
Support kan geen enkel record herstellen tenzij je elders een recycle bin bouwde. Ze hebben mogelijk een volledige backup-restore nodig, wat andere data kan beïnvloeden. Ze kunnen ook niet eenvoudig aantonen wat er gebeurde, wat leidt tot lange heen-en-weercommunicatie.
Restore-functionaliteit verandert ook de werkbelasting. Als gebruikers zelf binnen een venster kunnen herstellen, nemen tickets af. Als herstellen handmatig door support moet gebeuren, kunnen er meer tickets komen, maar ze worden snel en herhaalbaar in plaats van unieke onderzoeken.
Het “recht om vergeten te worden” betekent meestal dat je moet stoppen met het verwerken van iemands data en het verwijderen van plekken waar het nog bruikbaar is. Het betekent niet altijd dat je elke historische aggregate meteen moet wissen, maar het betekent wel dat je geen identificeerbare data “voor het geval dat” moet bewaren als je geen wettelijke reden meer hebt.
Hier wordt soft delete vs hard delete meer dan een productkeuze. Een soft delete (bijvoorbeeld deleted_at zetten) verbergt het record vaak alleen voor de app. De data staat nog in de database, is nog door admins opvraagbaar en zit vaak nog in exportbestanden, zoekindexen en analytics-tabellen. Voor veel GDPR-verzoeken is dat geen uitwissing.
Je hebt nog steeds een purge nodig wanneer:
Backups en logs zijn wat teams vergeten. Je kunt misschien geen enkele rij uit een versleutelde backup wissen, maar je kunt regels instellen: backups verlopen snel en herstelde backups moeten verwijder-events opnieuw toepassen voordat het systeem live gaat. Logs moeten idealiter geen ruwe persoonsgegevens opslaan en duidelijke retentiegrenzen hebben.
Een eenvoudige, praktische policy is een twee-stappen verwijdering:
Als je platform source- of data-export ondersteunt, behandel geëxporteerde bestanden ook als datastores: bepaal waar ze liggen, wie er toegang toe heeft en wanneer ze worden verwijderd.
Soft deletes klinken simpel: voeg een deleted_at (of is_deleted) vlag toe en verberg de rij. De verborgen kost is dat nu elke plek waar je data leest aan die vlag moet denken. Missen teams die filter één keer, dan ontstaan rare bugs: totalen bevatten verwijderde items, search toont “ghost” resultaten of een gebruiker ziet iets waarvan hij dacht dat het weg was.
UI- en UX-randgevallen duiken snel op. Stel dat een team een project genaamd “Roadmap” verwijdert en later probeert een nieuwe “Roadmap” aan te maken. Als je database een unieke regel op naam heeft, faalt de create omdat de verwijderde rij nog bestaat. Search kan gebruikers ook verwarren: als je verwijderde items in lijsten verbergt maar niet in globale search, denken gebruikers dat je app kapot is.
Soft delete-filters worden vaak vergeten in:
Performance is meestal in het begin prima, maar de extra voorwaarde voegt werk toe. Als de meeste rijen actief zijn, is filteren op deleted_at IS NULL goedkoop. Als veel rijen verwijderd zijn, moet de database meer rijen overslaan tenzij je de juiste index toevoegt. In gewone taal: het is alsof je zoekt naar huidige documenten in een lade met ook veel oude documenten.
Een aparte “Archive”-sectie kan verwarring verminderen. Laat de standaardweergave alleen actieve records tonen en plaats verwijderde items op één plek met duidelijke labels en een tijdvenster. In snel gebouwde tools (bijvoorbeeld interne apps gemaakt op Koder.ai) voorkomt deze productkeuze vaak meer supporttickets dan elk slim query-trucje.
Soft delete is niet één feature. Het is een datamodelkeuze en het model dat je kiest bepaalt alle volgende dingen: queryregels, restore-gedrag en wat “verwijderd” voor jouw product betekent.
deleted_at plus deleted_byHet meest voorkomende patroon is een nullable timestamp. Wanneer een record wordt verwijderd, zet je deleted_at (en vaak deleted_by naar de user id). “Actieve” records zijn die waar deleted_at null is.
Dit werkt goed als je een schone restore nodig hebt: herstellen is gewoon deleted_at en deleted_by leegmaken. Het geeft support ook een eenvoudige auditindicator.
In plaats van een timestamp gebruiken sommige teams een status veld met duidelijke staten zoals active, archived en deleted. Dit is handig wanneer “archived” een echte productstaat is (verborgen voor de meeste schermen maar toch meetelt in billing, bijvoorbeeld).
De kosten zijn regels. Je moet definiëren wat elke staat overal betekent: search, notificaties, exports en analytics.
Voor gevoelige of waardevolle objecten kun je verwijderde rijen naar een aparte tabel verplaatsen, of een event in een append-only log opnemen.
deleted_at, deleted_bystatus met benoemde statenDit gebruik je vaak wanneer restores strikt gecontroleerd moeten worden, of wanneer je een audittrail wilt zonder verwijderde data door alledaagse queries te mengen.
Child-records hebben ook een intentionele regel nodig. Als een workspace wordt verwijderd, wat gebeurt er met projecten, bestanden en lidmaatschappen?
archived (niet verwijderd)Kies één regel per relatie, noteer het en houd het consistent. De meeste "restore ging mis" bugs komen doordat parent- en child-records verschillende betekenissen van deleted gebruiken.
Een restore-knop klinkt simpel, maar kan stilletjes permissies breken, oude data op de verkeerde plek terugbrengen of gebruikers verwarren als “hersteld” niet betekent wat ze verwachten. Begin met het uitschrijven van de exacte belofte die je product doet.
Gebruik een kleine, strikte volgorde zodat restore voorspelbaar en auditbaar is.
Als je apps snel bouwt in een chatgestuurde tool zoals Koder.ai, neem deze controles als onderdeel van de gegenereerde workflow zodat elk scherm en endpoint dezelfde regels volgt.
De grootste pijn met soft deletes is niet de delete zelf, maar alle plekken die vergeten dat een record "weg" is. Veel teams kiezen soft delete uit veiligheid en tonen per ongeluk verwijderde items in zoekresultaten, badges of totalen. Gebruikers merken het snel als een dashboard “12 projecten” zegt maar er maar 11 verschijnen.
Op de tweede plaats staat toegangcontrole. Als een gebruiker, team of workspace soft-verwijderd is, mogen ze niet kunnen inloggen, de API aanroepen of notificaties ontvangen. Dit slipte vaak door omdat de login-check op e-mail zoekt, de rij vindt en de deleted-vlag nooit controleert.
Veelvoorkomende valkuilen die later supporttickets veroorzaken:
Uniciteit-botsingen zijn vooral vervelend tijdens restore. Als iemand een nieuw account aanmaakt met dezelfde e-mail terwijl het oude soft-verwijderd is, faalt een restore of overschrijft het mogelijk de verkeerde identiteit. Bepaal je regel van tevoren: blokkeer hergebruik tot purge, sta hergebruik toe maar verbied restore, of herstel onder een nieuw identifier.
Een veelvoorkomend scenario: een supportagent herstelt een soft-verwijderde workspace. De workspace komt terug, maar zijn leden blijven verwijderd en een integratie begint oude records naar een partnertool te syncen. Vanuit de gebruiker gezien werkte de restore “half” en veroorzaakte een nieuwe puinhoop.
Maak voor je release deze gedragingen expliciet:
Een B2B SaaS-team heeft een "Delete workspace" knop. Op een vrijdag voert een admin een cleanup uit en verwijdert 40 workspaces die inactief leken. Op maandag klagen drie klanten dat hun projecten weg zijn en vragen om direct herstel.
Het team dacht dat de beslissing simpel zou zijn. Dat was het niet.
Eerste probleem: support kan niet herstellen wat echt verwijderd is. Als de workspace-rij hard-verwijderd is en cascade projecten, bestanden en lidmaatschappen verwijdert, is de enige optie backups. Dat betekent tijd, risico en een ongemakkelijk antwoord naar de klant.
Tweede probleem: analytics lijkt kapot. Het dashboard telt “actieve workspaces” door alleen rijen te queryen waar deleted_at IS NULL. De per ongeluk verwijdering veroorzaakt een plotselinge daling in de grafieken. Nog erger: een weekrapport vergelijkt met vorige week en markeert een valse churn-piek. De data is niet verloren, maar werd op de verkeerde plekken uitgesloten.
Derde probleem: er komt een privacyverzoek binnen voor een van de getroffen gebruikers. Die vraagt om zijn persoonsgegevens te verwijderen. Een pure soft delete voldoet hier niet. Het team moet persoonlijke velden (naam, e-mail, IP-logs) purgen terwijl ze niet-persoonlijke aggregaten zoals factuurtotalen en factuurnummers mogelijk behouden.
Vierde probleem: iedereen vraagt: “Wie klikte op delete?” Als er geen spoor is, kan support niet uitleggen wat er gebeurde.
Een veiliger patroon is verwijdering als event met duidelijke metadata:
deleted_by, deleted_at en een reden of ticket-idDit is het soort workflow dat teams vaak snel bouwen in platforms zoals Koder.ai, en later beseffen dat het verwijderbeleid net zoveel ontwerp nodig heeft als de functies eromheen.
Kiezen tussen soft deletes en hard deletes gaat minder over voorkeur en meer over wat je app moet garanderen nadat een record "weg" is. Stel deze vragen voordat je één query schrijft.
Een eenvoudige manier om de beslissing te controleren is één realistisch incident doorlopen. Bijvoorbeeld: iemand verwijdert op vrijdagavond per ongeluk een workspace. Maandagochtend moet support het deletie-event zien, veilig herstellen en vermijden dat gerelateerde data wordt teruggebracht die verwijderd had moeten blijven. Als je een app bouwt op een platform zoals Koder.ai, definieer deze regels vroeg zodat je gegenereerde backend en UI één beleid volgen in plaats van speciale gevallen door de code te strooien.
Kies je aanpak door een eenvoudig beleid op te schrijven en te delen met je team en support. Als het niet op papier staat, zakt het beleid weg en ervaren gebruikers inconsistentie.
Begin met een duidelijk set regels:
Bouw daarna twee duidelijke paden die nooit door elkaar lopen: een “admin restore”-pad voor fouten en een “privacy purge”-pad voor echte verwijdering. Het restore-pad moet omkeerbaar en gelogd zijn. Het purge-pad moet definitief zijn en alle gerelateerde identificeerbare data verwijderen of anonimiseren, inclusief backups of exports indien je beleid dat vereist.
Voeg guardrails toe zodat verwijderde data niet teruglekt in het product. De makkelijkste manier is om “verwijderd” als een volwaardige staat in je tests te behandelen. Voeg review-checkpoints toe voor elke nieuwe query, lijstpagina, zoekfunctie, export en analytics-job. Een goede regel: als een scherm gebruikersgerichte data toont, moet er een expliciete beslissing zijn over verwijderde records (verbergen, tonen met label of admin-only).
Als je vroeg in een product zit, prototypeer beide flows voordat je het schema vastzet. In Koder.ai kun je het verwijderingsbeleid in de planningsmodus schetsen, de basale CRUD genereren en snel restore- en purge-scenario's uitproberen, en dan het datamodel aanpassen voordat je het commit.