Core and custom

An app built on Arc CMS never edits an Arc CMS file. Its own code goes in the custom space, which Arc CMS never touches, so every Arc CMS update merges cleanly.

The two spaces

Core is everything Arc CMS ships. Your app receives it by pulling updates (Upgrade Arc CMS) and uses it: importing a core component or helper is fine, editing it is not.

Custom is the set of files and folders Arc CMS ships once, empty, and never changes again: not a line of code, not a comment, not a README row. A new plug point comes as a new file, so an update only ever adds files there:

PathFor
src/custom/Pages and routes, admin menu items and changes to the admin and member menus, the member dashboard, where each role lands, translations, styles, installable app settings, feature choices
functions/src/custom/Cloud Functions and triggers, the callables the deploy checks, searchable collections
firestore.app.rules, storage.app.rules, firestore.app.indexes.jsonSecurity rules and indexes for the app's data (optional files, created when needed)
tests/rules/custom/The app's rules tests
docs/custom/The app's own docs
custom/The app's command-line scripts, the data they import, and design files: nothing that is bundled into the site
.github/The app's CI workflows (Arc CMS ships none)

How to use each file is in Use the custom space.

Core reads the custom space at fixed plug points: the routes file, the pages folder, the admin and member menus, the translations, the stylesheet, the feature choices, and the functions (functions/src/all.ts exports functions/src/custom/index.ts as the custom group). The rules build places your rules inside the core rules, so they can call the core helpers. Tests in src/app/custom-space.spec.ts check that every starter file ships empty, and that every file Arc CMS has shipped in the custom space is still exactly as shipped (SHIPPED_FILES in scripts/custom-starters.mjs holds a hash of each). They run only in Arc CMS's own repository, told apart the way npm run check:core does it (a remote at Arc CMS and no upstream branch), so filling the files in an app does not fail them.

What check:core reports

Run it before every commit in an app:

npm run check:core

It compares your working copy, committed or not, with the Arc CMS version your copy is based on (upstream/main or upstream/dev, whichever your copy follows, from the upstream remote), and sorts every difference into four kinds:

KindFilesResult
customThe custom space aboveFine
installsrc/environments/environment.ts, src/environments/environment.prod.ts, src/environments/arc-install.ts: this install's Firebase settingsFine
reviewpackage.json, package-lock.json, functions/package.json, functions/package-lock.jsonFine, listed so you look at them before the next update
coreEverything elseListed, and the check fails

It exits with 1 when a core file changed, so it can run in CI, and also when there is no upstream remote to compare with, so it never passes without checking. It prints the two commands that add the remote. A core file moved into the custom space still counts as a core change. To choose the Arc CMS branch yourself, name it with --against:

npm run check:core -- --against upstream/BRANCH

In the Arc CMS repository itself there is no core to protect, and the check says so and passes.

When the app needs something core does not offer

Stop, and do not edit the core file. The need becomes a general feature or a new plug point in Arc CMS, built there, and your app pulls it with the next update. The same goes for a core component that falls short: it gets a new option in Arc CMS. Copying it into the custom space loses every later fix, and editing it in place breaks the next update.

If you work on Arc CMS itself, keep it generic: nothing specific to one app, and nothing in the custom space beyond the empty starter files. See Keep core generic.

For the routine in an app (checking, fixing a flagged file, upgrading), see Keep core untouched.