🛍️ The Store (App Store)
NoCloud's sections aren't all hard-wired: many are modules you install and remove from the Store, the way apps work on a phone — except they run on your own server.
What a module is
Every module lives in its own folder with a manifest that explicitly declares everything it needs and does:
| Field | What it's for |
|---|---|
owns / read_tables / write_tables |
which database tables it reads and writes — its data permission |
secrets |
which credentials it needs (never shipped in the code) |
jobs |
scheduled tasks it brings along (e.g. an ingest job, or an hourly refresh — see below) |
migrations |
database changes it needs, run exactly once at install (each behind a snapshot) |
depends_on |
other modules that must already be installed — enforced both ways: you can't install an app before its dependencies, or remove one something else still needs |
published / recommended |
whether it appears in the Store's grid (opt-out), and whether onboarding suggests it |
preinstall |
present by default on a fresh instance (still normally uninstallable) — e.g. Photos |
widget |
a Home-grid widget this app contributes (slug, size, its own JSON feed) |
settings_tab |
a tab this app adds to the Settings app instead of (or in addition to) its own page |
assets / i18n |
translated strings it adds and removes on install/uninstall |
At a glance, you always know exactly what each app touches: nothing reaches data it hasn't declared.
Connectors: modules with database-write access
A connector is a special kind of module (module.kind: "connector") built to bring your own
files in: drop a bank export, a tax return, or a data export from another service, and it parses the
file itself — no AI involved — and inserts the rows into the right table.
Connectors have one elevated capability ordinary modules don't: writing to the core's database. That capability is disclosed up front — the Store shows exactly which tables a connector can write to before you install it — and it's tightly boxed in even after installation:
- A connector never writes SQL. It parses its file into plain rows and hands them to the core's ingest engine over an internal API; the engine is the only thing that ever touches the database.
- The engine validates every table/column name against a safe-identifier whitelist and binds every value as a parameter — the two things that make SQL injection possible are both closed off.
- A connector can only write the tables its own manifest's
permissions.write_tablesnames — the same declared, visible permission any other module has, just withwriteinstead of onlyread. Two independent gates enforce it: the connector's own manifest, and the dataset it's trying to write. - Every write is reversible (a snapshot is taken first) and idempotent — dropping the same file twice changes nothing the second time.
- If a file can't be processed, or only partly can, you get a short, plain-language log of exactly what went wrong, row by row — never a silent failure.
Connectors typically cover things like bank statements or tax declarations from a specific institution; each ships its own parser and file-format definitions, and you only install the ones that match banks or agencies you actually use.
Widgets and Settings tabs
Some apps are widget-only: Coming Up has no page of its own — just a Home widget and a
Settings tab where you paste your calendar link. module.kind: "widget" is for exactly that (no
entrypoint/output page to build). An ordinary "page" app can also declare a widget — Photos
does, for its "on this day" strip — a widget is independent of the app's own page kind.
A settings_tab works the same way as the built-in Permissions tab: the app's own generator exposes
a PANEL/CSS/SCRIPT, and the Settings app imports and injects them as one more tab, visible only
while the app is installed. This is for apps with configuration but no natural place on their own
page to put it (Photos' page is a redirect to Immich — there's nowhere on it to add a form).
A jobs[] entry normally runs on your workstation (host defaults to the legacy value "mac"). Setting host: "server"
plus a 5-field cron schedule (e.g. "17 * * * *") installs a real crontab line on the home
server instead — for something that has to run there regardless of whether your workstation is even on, like
Coming Up's hourly ICS refresh. Installing/uninstalling the app installs/removes that crontab line
automatically (scripts/server_cron.py).
Where modules live
Every installable section — including ones that ship with the core product, like Messaging
(WhatsApp), Mailbox, Takeout, and Webmail — is an ordinary module under the core repo's own
apps/store/, with the same manifest and install/uninstall lifecycle as everything else; there's
no hard-wired special case.
Connectors specifically always live in a separate, lighter-weight companion repository
(nocloud-store) dedicated to bank, tax, and other bring-your-own-file imports, so the core repo
never has to know a particular bank or agency exists. Other optional apps and pages can also ship
from that companion repo — the Store installs and shows a module exactly the same way regardless
of which repo it came from.
Some examples:
- Messaging (WhatsApp) — ships with the core: its own
chat_*tables, an ingest job, and the export detection that letsnocloud onboardrecognize a WhatsApp export. - Mailbox — ships with the core: ingests your mail archive and files it with readable rules you own; a suggest-a-rule helper drafts a rule from a plain-language description, and you review it before it ever runs.
- Takeout — ships with the core: recognizes a Google Takeout export (via
nocloud onboardor the Store) and routes its contents into the archive. - Webmail — ships with the core: the Mail section itself is a Store app, not a hard-wired special case.
- N26 / ING / AEAT connectors — live in the companion
nocloud-storerepo: each ships its own parser and only writes the tables its manifest declares (see Writing a connector).
Install and uninstall
- Installing adds the section to the catalog (it appears on everyone's home screen and in Settings) and deploys its code to the server.
- Uninstalling removes it from the catalog and deletes its code from the server. Its data is left untouched — still in the database, ready to pick back up if you reinstall it.
Only admins can install or uninstall; the action is audited.
Authorship
Every app shows its creator (by GitHub user) and links to its code, so you can always see where a piece came from. The catalog of available apps lives in the repository itself.
Tip
The Messaging module is a good example of an ordinary module: it ships its own
chat_* tables, an ingest job, and its own credential to reach the backup — all declared in its
manifest.