> For the complete documentation index, see [llms.txt](https://docs.emseapea.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.emseapea.ai/admin-guide/storage.md).

# Somewhere for apps to keep things

Every other page in this guide is about systems your apps **reach** — Salesforce, Microsoft 365, your ERP. This one is about the other half: the files and records an app creates **of its own**. An expenses app has to put uploaded receipts somewhere. A rota app has to keep the rota somewhere. Neither of those lives in a system your organization already has.

**Settings → Storage** is where you decide, once, which places are on offer. A builder then picks from your list instead of signing up for a bucket on a company card and telling nobody.

Every provider on that list except one is somewhere **you** set up, in your own account, before it is any use here. [Somewhere to keep files and data](/setting-up-your-accounts/somewhere-to-keep-files-and-data.md) covers what to create with each provider, and which of them Emseapea can currently do anything with.

## Read this before anything else on the page

> A storage class decides where an app keeps its data. It never decides what the app puts there.

That sentence is not a disclaimer at the bottom of a page — it is the whole shape of the feature, and everything below only makes sense once you have taken it seriously. **Nothing anywhere looks at what an app writes.** Your gateway passes request bodies through untouched, the deploy gate reads code scanner results and not traffic, and this screen reads nothing at all.

So: **a storage class does not stop an app storing personal data somewhere you did not intend it to go.** If you set up a class called "Rotas only, no personal details," an app given that class can still write someone's home address into it this afternoon and nothing will notice or say so. What you get is a **recorded, reviewable statement of where an app's data was meant to live**, and the ability to see later that an app's own declaration and its storage no longer agree. That is genuinely useful. It is not a control on contents, and no wording on this page will pretend otherwise.

## What a storage class actually is

Seven answers, given once, about one place:

| The question you answer                  | What it means                                                                                                                                                                      |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **What to call it**                      | The name a builder and an approver read. "UK customer files."                                                                                                                      |
| **What goes in it**                      | **Files** or **Database**. Not both — set up two classes if you need both.                                                                                                         |
| **Where it lives**                       | Which provider. Cloudflare R2, Amazon S3 or Azure Blob Storage for files; Cloudflare D1, Neon Postgres, Amazon RDS for PostgreSQL or Azure Database for PostgreSQL for a database. |
| **Where the data sits**                  | The country or region, in your own words. See [Region](#region-and-what-it-is-worth-today) below — it is usually the answer with legal weight.                                     |
| **Size limit**                           | An intended ceiling in whole gigabytes, or blank for none. A number to have a conversation about, not a quota — see [Size limits](#size-limits-are-a-warning-not-a-quota).         |
| **Which kinds of information it is for** | Which of your organization's information kinds this place may be **offered** for.                                                                                                  |
| **When an app using it is shut down**    | What should happen to the data. No default — you have to choose. See [What happens when an app is shut down](#what-happens-when-an-app-is-shut-down).                              |

A **database** class asks one more question — how an app should reach it — which is the single most consequential choice on the screen. It has [its own section](#how-an-app-reaches-a-database).

### Four worked examples

These four are the ones the product itself offers you on an empty Storage screen ("Add these four to start from"). They are a starting point, not advice — nothing in emseapea knows your obligations:

> **Everyday files** — Files, Cloudflare R2, no country chosen, 5 GB, delete the data straight away when an app is shut down, offered for "information anyone could see" and "everyday work information."
>
> The default home for the boring stuff: exported CSVs, generated PDFs, an uploaded logo. Most apps in most organizations want exactly this and nothing else.

> **Sensitive files** — Files, Azure Blob Storage, United Kingdom (UK South), 20 GB, keep the data for 365 days then delete it, offered for every kind of information including personal details, financial and health.
>
> The one you point at an Azure storage account **you already run**, in the region your legal team already signed off. The year of retention is there so somebody can answer a subject access request about an app that was switched off in March.

> **Everyday database** — Database, Neon Postgres, no country chosen, 1 GB, delete the data straight away, offered for everyday work information, reached **through your gateway**.

> **Sensitive database** — Database, Azure Database for PostgreSQL, United Kingdom (UK South), 10 GB, make a copy someone can collect then delete it, offered for every kind of information, reached **straight from the app**.
>
> Note the second one is not a softening of the first. Azure Database for PostgreSQL cannot answer a question over the web, so the gateway route is not on offer for it at all — emseapea refuses that pairing rather than recording an arrangement nothing could deliver. The starting set contains one of each route deliberately, so you meet the trade-off here rather than the day an auditor asks what a live app has been doing.

### Which kinds of information a class is for

Ticking the information kinds decides **which apps get offered this place**, and that is all it does:

> Ticking a kind of information decides which storage a builder is offered for it. Nothing looks at what an app actually writes, so this cannot stop an app putting the wrong thing in the wrong place.

A class must be ticked for **every** kind of information a request declared before it appears to that builder. A class ticked only for "information anyone could see" will not be offered to an app that says it handles personal details — which is the point of ticking them. It is a menu filter, and the app's declaration behind it is **self-declared and unverified**.

There is a second, separate filter since templates arrived: a builder is also only offered the storage classes allowed for **the template their app is built from** (Settings → Templates). Both have to allow a class before it appears. The refusal messages are deliberately different, because one sends you to Settings → Templates and the other to Settings → Storage.

### When an app's declaration and its storage stop agreeing

Six months after an app was approved, somebody updates what it handles to include personal details — and its files are sitting in a class nobody ticked for that. emseapea works this out every time anybody looks, and shows it on the app's own page, with the classes on your list that would suit.

Two things it deliberately does **not** do, and both are said on screen:

> Nothing has been moved and nothing has been switched off. This app carries on exactly as it did.

Moving a live file store or database needs a plan for when the app goes offline and how it comes back. emseapea holds no key to your storage and cannot make that plan, so it hands you the facts and names who to talk to. The finding clears on its own once the two sides agree again — either what the app handles changes, or somewhere suitable is recorded as where it keeps its data.

## Region, and what it is worth today

Region is usually the field on this screen with actual legal weight. "Does this stay in the UK?" is a question your DPO asks and an auditor checks, and it is why the field is required and free text rather than a dropdown of vendor region codes — you are answering a jurisdiction question, not an infrastructure one.

**And today nothing acts on it.** The screen says so beside the box:

> Recorded for your records, and not yet applied to anything. Emseapea does not turn these words into a provider location — a Cloudflare R2 file store is created wherever Cloudflare puts it, rather than in a region guessed from this box. Everywhere else on this list is somewhere you set up yourself, in whatever region you chose then.

Read that carefully, because the two halves apply to different rows of your list:

* **For the one provider emseapea creates storage on — Cloudflare R2 —** the bucket is created without a location hint, and Cloudflare decides where it goes. Writing "United Kingdom" in the box does not make that happen. Refusing to guess is the right call (a bucket quietly created in Virginia under a class labelled "United Kingdom" would be worse than an honest gap), but it does mean the field is a record of intent.
* **For every other provider** — S3, Azure Blob, and all four databases — emseapea creates nothing at all. The place already exists, in whatever region **you** chose when you made it, and the box is where you write that down so an approver can read it.

If jurisdiction is load-bearing for a class, the honest arrangement today is to create that storage yourself in the region you need, and use the region field to record what you did.

**One thing to know if you are an approver rather than the person who set the class up.** The caveat above is on the Storage screen, beside the box. It is *not* repeated on the request or approval card, which shows the region as a plain line — **"Kept in: United Kingdom"** — alongside where the data goes and what happens at shutdown. Read that line as the intent recorded on the class, not as a verified fact about where a Cloudflare R2 bucket physically is.

## What is actually created, and what is only written down

> Defining a class creates nothing on its own. This is the list of places you have agreed apps may use. A Cloudflare R2 file store is set up for real the next time you deploy your gateway; everything else on this list is a place you already have.

**Cloudflare R2 is the only provider emseapea provisions.** Deploying your organization's gateway creates the bucket, mints a key that reaches that bucket and nothing else, and puts that key on your gateway as a secret — never in emseapea's database. Amazon S3, Azure Blob Storage, Cloudflare D1, Neon, Amazon RDS and Azure Database for PostgreSQL are **names on a list**: choosing one records a decision, and you set the storage itself up yourself.

### Two R2 files classes are one bucket

The consequence most likely to catch you out, said on the screen at the moment you pick the provider:

> Two Cloudflare R2 file classes in one organisation are the same store. Emseapea sets up one file store per organisation and environment, so a second class is another name for the same place rather than a separate one. Each app still keeps its files in its own folder there and reaches only that folder — your gateway puts the folder in front of every file an app asks for, and refuses anything outside it.

Both halves matter and they pull in opposite directions. If you define "Sensitive files" next to "Everyday files" and both are on Cloudflare R2, they are **the same bucket under two names** — there is no separation between them beyond the labels. But apps are genuinely kept apart inside it: your gateway puts each app's own folder in front of every path it asks for, and a path trying to climb out of that folder is refused outright rather than quietly tidied up. One app cannot read another's files.

If you want a real separation between two classes of file — a different bucket, a different account, a different country — put the second one on storage you set up yourself and name the provider accordingly.

As with region, this caveat is on the Storage screen and not on the approval card, where two Cloudflare R2 classes read as two named places. If it matters to a decision you are about to make, check which provider each class is on.

## Size limits are a warning, not a quota

> Recorded so an approver can see the intended ceiling. Emseapea checks each app against it daily where it can read what that app is using, and says so plainly where it cannot rather than showing you a zero. Nothing is ever turned away at this number: it is a warning to act on, not something your storage itself keeps to.

And the decision behind it, said out loud rather than left to be discovered:

> Nothing is turned away when an app goes past its size limit. Emseapea is not between your apps and their own storage — it cannot see what they write, and the figure here is one it reads on a schedule rather than as it happens, so turning a write away on it would refuse data that fits and let through data that does not. Passing the limit is a conversation to have, not an outage.

You get a warning at four fifths of the limit, and it keeps going past it. Nobody's app breaks at the number.

**What you will actually see today: "Not available", for every app and every place** — and note that the daily reading the sentence above describes is itself scheduled but not yet switched on in any environment. That is not a fault, and it is deliberately not shown as a zero:

> Not available means nobody could read this figure — not that this app is using nothing. Treat it as a question still to be answered.

The reason is on each app's own page, and it is almost always the same one. For a Cloudflare R2 store, emseapea created the bucket and **kept no key to it** — the key went straight to your gateway and was never written down — so it cannot open the store to measure it. For every other provider emseapea holds no account at all, so there is nothing to ask; whoever looks after that storage can see the figure in their own provider console.

The number will come from your own gateway in a later release, since that is where the key already lives and where each app's folder is already known. Minting a fresh account-wide read key nightly was considered and refused: it would mean the product holding, once a day, a key that reads every customer file in the account — in order to put a number on a screen.

## What happens when an app is shut down

Three behaviors, and **there is no default** — you have to pick one before a class can be saved, and an app using a class with no rule **cannot be shut down at all** until somebody sets one. A blank field must never quietly propose destroying your data.

* **Delete it straight away** — the app's data in this class goes when the app does.
* **Keep it for a set number of days, then delete it** — you give a number between 1 and 3650 (ten years). This is the one people misread, so: **the data really is still there for those N days.** It is deliberately not deleted; the shutdown is recorded as **"Being kept until a set date"** with a dated deadline, and it is listed on your Storage screen from the moment the app is shut down — oldest debt first — until somebody deals with it. This is how you answer a question about an app three months after it was switched off.
* **Make a copy someone can collect, then delete it** — an export is produced and can be collected from the app's page by an administrator or approver of your organization, and nobody else.

There is deliberately **no "keep it forever."** Keeping data indefinitely is the thing data protection rules exist to rule out, so the only way to keep data here is to say for how long.

### What the export is, and what it is not

The export is **not a copy of the app's files or database rows.** emseapea has never held a byte a governed app wrote. It is the record: where the app's data was placed, under which rule, with which deadline, and the app's full event history up to the shutdown. That is the useful half of "what was this app, what did it touch, and where did its data go?" — the bytes themselves are still with your provider, which is why the document ends by naming the folder and the date.

### The part you have to do yourself

> There is no default — an app cannot be shut down until this is set. Shutting one down reads this rule, records what it decided, and schedules the deletion. Emseapea holds no key to your storage, so the final step of actually removing the data is listed for an administrator to do; it is not done for you.

**No bytes are removed by emseapea, for any provider, ever, today.** The decision is made, recorded, dated and put in the audit trail; the last step is a task with a named owner, marked **"Waiting for someone to do it"** on your Storage screen. For a Cloudflare R2 store the task names the exact folder to delete, and explains why the whole bucket is not deleted instead: every other app's files are in there too.

Treat "keep for 90 days, then delete" as **"remind me, with the folder name, on day 90"** — which is a genuinely useful thing to have and is not the same as automatic deletion. Nothing on the Storage screen ever reaches "Done", because there is nothing that could put it there.

A daily job is what moves an item from **"Being kept until a set date"** to **"Waiting for someone to do it"** on the day it falls due, and it is the same daily job that takes the storage-usage readings above. **It is written and scheduled but has not yet been switched on in any environment**, so today an overdue item stays under its original label with a date in the past rather than changing state. It is still listed, still dated, still oldest-first, and nothing is deleted either way — but if you are relying on retention dates, sort by the due date yourself rather than waiting for the label to change.

## How an app reaches a database

For a database class only, you choose how apps get to it. This is the choice an approver most needs to understand, so the consequence is shown as a badge on the option, again on the class in your list, and again on the page of every app given it.

**Through your gateway.** The app asks your own gateway for what it needs over the web. The only key it holds is its revocable handle.

> This app holds no database password — only a handle you can revoke.

**Straight to the database.** The app connects itself, using a connection string you record here, which emseapea keeps encrypted and hands to the app when it is deployed.

> This app holds a database password.

|                      | Through your gateway               | Straight to the database |
| -------------------- | ---------------------------------- | ------------------------ |
| The app holds        | Only its revocable handle          | A real database password |
| Works with           | Databases that answer over the web | Any database, any ORM    |
| Appears on the graph | **Yes** — every call               | **No**                   |
| If the app leaks     | Nothing that outlives a revoke     | That app's own database  |

**Losing sight of it on the graph is the bigger cost, not the password.** A revoked handle is recoverable; a year of database activity nobody could see is not. A direct connection means emseapea cannot tell you what that app actually did with its database — you can see what it was **given**, never what it **used**. That is said in plain words on the app's own page rather than left to be discovered.

Not every database can offer both routes. The gateway needs somewhere to pass a request on to, so a database that answers only over a database connection does not offer that choice at all, and the screen says which database and why rather than silently dropping the option. Cloudflare D1 and Neon can answer over the web; Amazon RDS for PostgreSQL and Azure Database for PostgreSQL cannot.

**One honest gap.** The gateway route is not carrying traffic yet:

> Not set up yet: Emseapea creates a shared file store and routes apps to it, but it does not yet put a database behind your gateway. This records how you have decided apps should reach it.

A class set to the gateway route records the arrangement you have chosen and nothing serves it today — a call from an app to that address is refused. The builder-facing documentation generated into the app's repository says so outright and gives no example to copy. The direct connection is the route that works end to end right now. If you set a class to the gateway route, tell your builders it is not something to build against yet.

## Taking a class off the list

Two buttons, and they do very different things.

**Stop offering it** (retiring) is the one you almost always want:

> Retiring takes this off the list for anything new. Apps already using it carry on unchanged — no data moves and nothing is deleted.

A retired class stays visible in Settings, marked, so an administrator looking at an old app can see why it references something no longer on offer. "Put it back" undoes it.

**Delete** removes the row entirely, and is refused while any app — including a decommissioned one — still references the class. Deleting it would destroy the only record of where those apps' data lives, which nobody could then look up. It would not delete the data: emseapea holds neither. Hard delete survives only for a class nothing has ever used.

## Where to go next

* [Approvals](/admin-guide/approvals.md) — the storage a request asks for appears on the approver's card, in exactly the words the requester saw.
* [Decommissioning and audit export](/admin-guide/decommission-and-audit.md) — the wider shutdown flow this page's retention rules feed into.
* [Storing files and data in your app](/builder-guide/storing-files-and-data.md) — what your builders see, and the worked examples generated into their repositories. Worth reading before you set your list up, so you know what you are handing them.

The reasoning behind all of it — including every option that was rejected and why — is recorded as **D19, "The storage class model,"** in the [product's architecture decisions](https://github.com/scmjea/emseapea/blob/main/docs/architecture.md) (engineering repo access required).
