Architecture
Arc CMS is an Angular app on Firebase. The browser talks to Firestore, Auth and Storage directly, under security rules; Cloud Functions do everything the browser must not, and publish the public pages as plain files on Firebase Hosting.
The pieces
| Piece | What it does | Where it lives |
|---|---|---|
| The app | AnalogJS (full-stack Angular), built with Vite. It is the admin area, the member area, the sign-in pages and the fallback for every public address. | src/app/, src/shared/, src/custom/ |
| Firestore | All data: content, settings, people, lists, logs. One database per install, (default) unless the install names another. | Rules in firestore.rules |
| Firebase Auth | Sign-in. A person's role travels as a custom claim on their ID token (see Roles and claims). | functions/src/auth/, functions/src/users/ |
| Cloud Storage | Media, imports and exports, and each person's own folder. | Rules in storage.rules |
| Cloud Functions | Triggers on database writes, callables the browser calls, scheduled jobs and a few public HTTP endpoints (webhooks, unsubscribe, email tracking). Node.js 22. | functions/src/ |
| Firebase Hosting | Serves the built app and the pages the functions publish. | firebase.json |
How pages are served
No server runs when someone opens a page. Firebase Hosting serves files:
- Published content (content pages, list pages, static pages, the sitemap, the feeds,
robots.txtandllms.txt) is HTML and text that Cloud Functions render from the site's templates (Arc CMS's inpublic/_site/templates/, the app's insrc/custom/site/templates/) and write to Hosting whenever something is published. - The home page is rendered once, at build time.
- Two email addresses,
/unsubscribeand/email-preferences, are sent to Cloud Functions by Hosting rewrites, so the links in emails stay on the site's own address. - Every other address that is not a file gets the app shell,
__shell.html, and Angular renders the page in the browser. That covers the admin area, the member area, and any content page not published as a file yet.
During development, npm run dev also renders pages on the server. Pages that need a signed-in person are always rendered in the browser.
The three areas
| Area | Addresses | Who gets in |
|---|---|---|
| Public site | /, content and static pages, /search, signup forms | Everyone |
| Member area | /user/dashboard, /user/profile, and your app's own pages | Anyone signed in (route guard in src/app/pages/user/user.guards.ts); premium pages need an entitlement |
| Admin area | /admin/... | Admins (route guard in src/app/guards/role.guard.ts) |
The sign-in pages (/signup) and the first-run setup (/onboarding) sit between them. The route guards only decide what the browser shows: the security rules and the function checks are what protect the data. See What members see and Use the admin area.
How the functions are grouped
functions/src/index.tsexports one group,arccms, made of everything infunctions/src/all.ts. So every function deploys asarccms-plus its name, such asarccms-search, and belongs to thearccmscodebase infirebase.json.- A switchable feature's functions live in
functions/src/features/and are built in only when the feature is on insrc/custom/features.ts. See Feature ids. - Your app's functions, from
functions/src/custom/, form thecustomgroup and deploy asarccms-custom-plus their name. - The browser calls a function by its plain name through
arcCallable()insrc/app/core/config/arc-functions.ts, the one place that adds the prefix. - Every function reads and writes the install's database through
dbfromfunctions/src/init.ts, and every Firestore trigger passes its path througharcDocument()fromfunctions/src/arc-config.ts, so it listens on that database.
The full list is in Cloud Functions.
Install settings
Which database, bucket, upload folder and hosting site an install uses comes from arccms.config.json. npm run arc:configure and the guided deploy turn it into one file per consumer:
| Read by | File |
|---|---|
| The app (browser and build) | src/environments/arc-install.ts, read only by src/app/core/config/arc-config.ts |
| The functions | functions/.env.{projectId}, read only by functions/src/arc-config.ts |
| The Firebase CLI | firebase.{projectId}.json, used by npm run deploy |
With no settings, an install uses the (default) database, the project's default bucket and its main hosting site. See Configure a project and Sharing a Firebase project.
The main data flows
| When | What happens |
|---|---|
| An editor publishes content | The draft lives in arc_{slug}_drafts. Publishing adds an item to _publish_queue; the processPublishQueue function copies the draft to arc_{slug}, renders its page, the list pages, the sitemap and the feeds, publishes them to Hosting as one release, and updates the search index. |
| Someone signs up | A callable creates the users record on the server. onUserRoleChange puts the role and the record id on the account as claims, which the rules read. |
| An admin changes a role | The admin writes role on the users record; onUserRoleChange copies it to the arccms_role claim. |
| Something happens to a person | Core writes an event to AppEvents (such as user.signed_up, user.deleted or user.signInDeleted). Automations, and your app's own triggers, react to it. |
| A person is deleted | Deleting the users record starts onUserDeleted, which ends their access, removes their sign-in (unless another app shares it), their data under the record and their storage folder, then writes user.deleted. See Follow the account contract. |