GDPR & Compliance · 9 min read · Updated 15 August 2026
Building GDPR-compliant apps: a practical checklist for EU founders
GDPR applies to your app the moment it stores anything that identifies a person, including a single e-mail address in a contact form. The four things that matter in practice are consent before non-essential storage, a documented answer to where each data category sits, a working deletion path for every user, and collecting less in the first place. This is an engineering checklist, not legal advice.
Start with what counts as personal data
GDPR applies as soon as your app processes data that can identify a person — an e-mail address in a contact form is enough. Most small apps cross that line immediately, which is why it is worth handling from the first version rather than retrofitting later.
Please note this is a practical engineering checklist, not legal advice. For anything with real exposure, talk to someone qualified in your jurisdiction.
Consent before storage, not after
The rule people most often get backwards: you need consent before setting non-essential cookies or storing tracking data, not after the fact. Practically that means analytics scripts must not load until the visitor accepts, and a consent banner has to offer a genuine decline option that is as easy to click as accept.
The same applies inside your own app. If you save a draft, a preference or a session identifier to local storage for convenience rather than necessity, gate it behind the same consent check.
Know where your data physically sits
You should be able to answer, for every category of data your app touches, where it is stored and who can access it. That includes the obvious database, but also less obvious flows: analytics, e-mail delivery, payment processing and any AI service the app calls. Each of those is a processor in the chain.
This is where generation method starts to matter. When AI generation runs locally in the browser, the prompt is processed on the user's own device and never becomes a transfer to a third party at all. When it runs in the cloud, it is one more processor to account for.
The concrete answers for an app built here
Since the point of this section is that you should be able to answer the question rather than gesture at it, here are the answers for a default NorthernGo app.
App data goes to the built-in Supabase PostgreSQL database through window.NorthernGoDB. The infrastructure sub-processors are Supabase and Firebase, and customer application data and hosting run on servers within the EU/EEA. The platform itself is operated by a sole trader, Nicklas Långberg, based in Lycksele, Sweden — which is worth knowing precisely because "who exactly is the processor" is a question you are expected to be able to answer about your suppliers, not just your own company.
Cloud code generation uses Google Gemini models. That is a processor in your build chain rather than in your app's data path: it sees your prompt, not your users' records. Local generation through WebGPU removes it from the picture entirely, which is a genuine difference and also a smaller one than it sounds — the local versus cloud comparison goes into where each one lands, but for compliance purposes the finished app's data handling matters far more than how the code was written.
Who is the controller, and who is the processor?
Getting this backwards causes more trouble than any technical mistake, so it is worth stating flatly.
You are the controller for the data your app collects. Your users' records belong to your relationship with them. NorthernGo acts as the processor for the default built-in storage, and accepting the platform terms enters a standardised data processing agreement covering that storage.
What follows from that is the part people do not expect: fulfilling your end users' rights is your responsibility, not the platform's. If one of your users asks for a copy of their data or asks you to delete it, that request lands on you. Nobody will handle it on your behalf, and "the platform stores it" is not an answer. Which is precisely why the next section is not optional.
The delete-account button is not optional
This is the one place where the platform stops being neutral and enforces something.
Any generated app that has login must also ship a visible way to delete the account. It is written into the generation rules as a GDPR article 17 requirement, and it is not a suggestion the model can weigh against other priorities. In practice that means:
- A clear button in the profile, settings or account menu, next to the sign-out action, styled as a destructive action.
- A confirmation step first. A click must never delete immediately; the dialogue has to state plainly that the account and all stored data are permanently removed and cannot be recovered.
- The actual deletion goes through
await window.NorthernGoDB.deleteAccount(). Building your own deletion route withfetch, or trying to clear the user by looping overdelete(), is explicitly ruled out — a hand-rolled version reliably leaves the auth record or some of the data behind. - A receipt afterwards, then back to the signed-out view.
if (confirm('This permanently deletes your account and all your data. Continue?')) {
try {
await window.NorthernGoDB.deleteAccount();
showMessage('Your account and all your data have been deleted.');
location.reload();
} catch (err) {
showMessage(err.message);
}
}
The rule also survives edits. When you ask for a change to an existing app, the generator preserves your design and features — with one deliberate exception: if the app has login but is missing the delete-account button, it gets added during that update, and an existing one is never removed. It is the only feature the platform will add to your app without being asked. See the database and login API for how account scoping works underneath.
Custom Database moves the responsibility to you
The Custom Database setting mirrors every write your apps make to an HTTP endpoint of your choosing, as JSON. It is genuinely useful, and it changes your position.
Data flowing to your own endpoint is outside the platform's visibility. Nothing is retained or reviewed on that path, and the platform is explicitly not acting as your processor for it. That means two things you have to write down yourself: what that endpoint does with the data, and where it is. If the receiving system sits outside the EU/EEA, you have introduced a transfer that the default setup did not have, and it is yours to document and justify. The same applies to webhook integrations and to your own Stripe keys.
Right of access, practically
Erasure gets all the attention and access requests cause more scrambling, because there is no button for them. The realistic approach is to build the export while you still remember your own data model.
You already have the pieces. window.NorthernGoDB.get(collection) returns the records for the signed-in user, and window.NorthernGo.exportCSV(records, 'my-data.csv') turns an array into a downloadable file. A "Download my data" button next to the delete button costs about ten lines and turns a stressful e-mail into a link you send.
Data minimisation is a design decision, not a policy
Every field you add to a form is a field you have to protect, justify, locate on request and eventually delete. The cheapest compliance measure available is not collecting the data.
Two specifics are worth naming. The generator will proactively add basic privacy UI when an app collects personal data — a dismissible cookie banner and privacy-policy checkboxes on forms. Treat that as a starting point rather than a result: a banner is a user interface element, and compliance depends on whether it actually blocks something. A generated app does not load analytics by default, so quite often there is nothing behind the banner to gate, and the honest move is either to remove the banner or to wire it to whatever you added.
The second is local storage. Draft text, remembered filters and cached preferences are storage, and convenience storage needs consent. If your app writes to localStorage for anything other than keeping the session working, it belongs behind the same check — and if it is holding app data rather than preferences, it should probably be in the database instead.
What the platform does not do for you
An honest list, because assuming otherwise is how people end up exposed.
- No record of processing activities. Nothing generates or maintains your documentation.
- No retention limits. Collections have no time-to-live. A record you save stays until something deletes it, and "we keep data for twelve months" is a promise you have to implement yourself.
- No consent log inside your app. The generated banner records a choice in the browser, not an auditable trail.
- No data-subject-request workflow. No inbox, no timers, no templates. Requests arrive as ordinary e-mail to you.
- No schema enforcement. Nothing stops an app from saving a field you never intended to collect, which is a data minimisation problem as much as a data quality one. Read what you actually write.
- No legal review. Nothing here, including this page, is legal advice.
Checklist before launch
- A consent banner with a real decline option, blocking analytics until accepted.
- A privacy policy that names your actual processors, not a template list.
- A documented answer to where each data category is stored.
- A working way to delete a specific user's data on request.
- Forms that collect only what you genuinely need — every extra field is extra liability.
- Explicit consent checkboxes on anything that publishes user content publicly.
- A visible delete-account action in every app that has login, behind a confirmation.
- A retention decision per collection, written down, and code that enforces it.
- An export path for access requests, ideally as a button rather than a manual process.
- If you use a custom endpoint, webhooks or your own payment keys: a documented answer to where that data goes and whether it leaves the EU/EEA.
One last point that is easy to miss. Portability is a data protection matter as well as a commercial one — the right of access assumes you can get the data out, which is hard if your app and its records are locked inside a platform. That overlap is covered in the guide on avoiding vendor lock-in, and it is a good reason to test your own export before someone else asks you to.
Frequently asked questions
Does using AI to generate my app affect GDPR compliance?
It depends on where the generation runs. Cloud generation sends your prompt to an external provider, which becomes another processor to account for in your documentation. Local generation on your own device is not a transfer to a third party at all. Either way, what matters most for compliance is how the finished app handles user data.
Do I need a cookie banner if my app only stores a login session?
Storage that is strictly necessary to deliver a service the user asked for, such as keeping them logged in, generally does not require consent. Analytics, advertising and convenience storage do. The safe approach is to gate everything that is not strictly necessary behind consent.
Do apps built with NorthernGo have a way for users to delete their account?
Yes, and it is enforced rather than optional. The generation rules require every app with login to ship a visible delete-account action, behind a confirmation dialogue, calling window.NorthernGoDB.deleteAccount(). If an existing app has login but is missing that button, it is added the next time the app is updated, and an existing one is never removed.
Where is the data my app collects actually stored?
In the built-in Supabase PostgreSQL database, on servers within the EU/EEA. The infrastructure sub-processors are Supabase and Firebase. If you configure the Custom Database setting, writes are additionally mirrored to an HTTP endpoint you choose, and that path is outside the platform’s visibility — where it goes and whether it leaves the EU/EEA is yours to document.
Build it yourself
NorthernGo turns a plain-text description into a working web app with a database, login and a live URL. Local AI generation runs on your own GPU, is unlimited, and is free on every plan.