Use the custom space

All of your app's code goes in a handful of files and folders that Arc CMS ships once, empty, and never changes again. This page lists each one, what it is for, and where Arc CMS reads it.

The files

In the browser app, src/custom/:

FileWhat it holdsRead
src/custom/features.tsCUSTOM_FEATURES: the Arc CMS features the app turns off, and the installable app turned onChoose your app's features
src/custom/languages.tsMEMBER_LANGUAGES: languages the app adds for its members, with their files in src/custom/i18n/Member languages
src/custom/routes.tsCUSTOM_ROUTES: the app's pages as Angular routesAdd pages and routes
src/custom/pages/Page files found by name, like src/app/pages/ (learn.page.ts is /learn)Add pages and routes
src/custom/nav.tsCUSTOM_NAV: the app's items in the admin menu. It also ships an empty CUSTOM_NAV_CHANGES, from before nav-changes.ts existed: still read, but write new changes in nav-changes.tsChange the admin menu
src/custom/nav-changes.tsCUSTOM_NAV_CHANGES: the app's changes to the admin and member menus, by item id and roleChange the admin menu, Change the member menu
src/custom/user-dashboard.tsCUSTOM_USER_DASHBOARD: the app's own page at /user/dashboardBuild the member area
src/custom/home.tsCUSTOM_HOME: where each role lands after signing inBuild the member area
src/custom/app-accounts.tsCUSTOM_APP_ACCOUNTS: whether the accounts your app manages may open the member pagesApp accounts
src/custom/sign-in.tsCUSTOM_SIGN_IN: how hard new passwords and PINs must be (strict, or simple for casual apps)Set up sign-in
src/custom/i18n/en.json, src/custom/i18n/hi.jsonThe app's translations, and new wording for core onesLanguages
src/custom/styles.cssThe app's global styles, and the admin's colours (--arc-admin-*)This page, Brand the admin
src/custom/brand.ts, src/custom/logo.svg (or .png, .webp)CUSTOM_BRAND: the site's name until an admin sets one, and core pages' own tab titles; the logo beside the nameBrand the admin
src/custom/sign-in-image.webp (or .jpg, .jpeg, .png, .svg)An image that fills the sign-in page's brand panelBrand the sign-in page
src/custom/pwa.ts, src/custom/pwa-icon.svg (or src/custom/pwa-icon.png)CUSTOM_PWA: the installable app's name, colors, start page, and its icon (or name each brand's icon with icon)Make it installable
src/custom/site/The public website: the home page (home.html, home.{lang}.html), header, footer, the sign-in page's brand panel (sign-in.html), content templates, static pages, site styles, translations of template text, images and scripts, favicon and error pages. Every file optional; Arc CMS ships noneBuild your home page, Design templates, Write static pages

In the Cloud Functions, functions/src/custom/:

FileWhat it holdsRead
functions/src/custom/index.tsThe app's functions and triggers, exported from hereWrite your own Cloud Functions
functions/src/custom/public-callables.txtThe app's callables, one name per line, for the check that they are reachableWrite your own Cloud Functions
functions/src/custom/search-sources.tsSEARCH_COLLECTIONS and CUSTOM_SEARCH_SOURCES: what the app makes searchable besides contentSearch

At the root and in the test and docs folders. Arc CMS ships nothing in these beyond a README; you create the files when you need them:

FileWhat it holdsRead
firestore.app.rules, storage.app.rulesThe app's security rules, added to the core rules at deploySet security rules and indexes
firestore.app.indexes.jsonThe app's Firestore indexes, merged with the core onesSet security rules and indexes
tests/rules/custom/Tests of the app's rulesSet security rules and indexes
docs/custom/The app's own documentation, in any formatWriting docs
custom/Everything that is not part of the built site: command-line scripts, the data they import, design files. Arc CMS ships only its README.Run scripts and keep data
.github/The app's CI workflows, such as .github/workflows/deploy-staging.yml. Arc CMS ships none.Deploy from CI

Where Arc CMS reads each file

Each custom file has one plug point: a place in core that reads it. You never edit these core files; they are listed so you know what happens to your code.

Custom fileRead byWhat happens
src/custom/routes.tssrc/app/app.routes.tsAdded after every core route, so an app page cannot replace a core page by accident, and before the file-based pages
src/custom/pages/vite.config.tsScanned for page files next to src/app/pages/ (additionalPagesDirs)
src/custom/nav.ts, src/custom/nav-changes.tssrc/shared/components/side-navbar/side-navbar.component.ts, src/app/pages/user/user-shell.component.ts, through src/app/core/nav/CUSTOM_NAV is inserted before Profile in the admin menu; an item with a feature is left out when that feature is off. Then CUSTOM_NAV_CHANGES is applied, the list in nav.ts first and then the one in nav-changes.ts (src/app/core/nav/app-nav-changes.ts), to each menu for the signed-in person's role, after the items of features that are off are gone. Profile, Logout and the member menu's Sign out button always stay. src/app/custom-space.spec.ts fails on an id the menu does not have and on a link that is not a path or an https address. Empty: both menus as Arc CMS ships them
src/custom/user-dashboard.tssrc/app/app.routes.tsLoaded at /user/dashboard in place of the blank core page
src/custom/home.tssrc/app/core/home/home.tsLaid over the defaults (admins to /admin/dashboard, everyone else to /user/dashboard)
src/custom/app-accounts.tssrc/app/core/app-accounts/app-account-lock.tsRead by the guard on Arc CMS's member pages and the member menu. Empty: locked app accounts may open them, read-only
src/custom/i18n/src/app/core/i18n/translation.loader.tsFound by file name. Merged over the core translation file key by key: new keys are added, existing keys are reworded. A language core does not have is the app's file alone, with English filling any gap
src/custom/styles.cssindex.htmlLoaded as the last stylesheet, so its rules win; the --arc-admin-* variables it sets are read by src/admin-theme.css
src/custom/brand.ts, the logo and the sign-in imagesrc/app/core/brand/site-brand.ts, src/app/core/brand/app-brand-files.ts, src/app/core/brand/brand-title.strategy.ts, vite.config.tsThe name and logo come after Settings, About; the files are found by name at build time; the name is also index.html's title
src/custom/pwa.ts and the iconvite.config.ts, src/app/core/pwa/pwa.service.tsLaid over the core defaults at build time; the icon is found by its file name
src/custom/site/scripts/arc-site.mjs, through scripts/vite-arc-site.ts in vite.config.tsLaid over Arc CMS's public/ and public/_site/ when npm run dev and npm run build start, file by file: a file at the same served path replaces Arc CMS's, strings files are merged key by key. The result is served at /_site/ and listed in /_site/site.json; the app and the publish functions read only that
src/custom/features.tsvite.config.ts, src/app/core/features/features.ts, scripts/arc-features.mjsChecked when the build starts, then decides which pages, menu items and functions exist
src/custom/sign-in.tsvite.config.ts, src/app/core/sign-in/sign-in-strength.ts, scripts/arc-features.mjsChecked when the build starts; the pages read it, and the functions read the copy the script writes to functions/src/sign-in.gen.ts. Empty: strict
functions/src/custom/index.tsfunctions/src/all.tsExported as the custom group, so each function deploys as arccms-custom- plus its name
functions/src/custom/public-callables.txtfunctions/scripts/check-callable-access.sh, scripts/arc-deploy-menu.mjsThe listed callables are checked for public access after a deploy
functions/src/custom/search-sources.tsfunctions/src/search/Each named collection gets a search trigger
firestore.app.rules, storage.app.rules, firestore.app.indexes.jsonscripts/arc-rules-build.mjsCombined with the core files before every rules or indexes deploy
tests/rules/custom/vitest.rules.config.tsRun with the core rules tests by npm run test:rules
custom/vitest.config.ts; your scripts import scripts/arc-admin-script.mjsIts *.spec.ts files run with npm run test; runAdminScript gives a script the project, database and credentials

Styles and translations

Put global styles in src/custom/styles.css. Styles for one page belong in that page's component. Styles for the public website (header, footer, templates) go in src/custom/site/site.css, and the words of the website's templates in src/custom/site/strings/ (see Publish in more than one language).

Add the app's words to src/custom/i18n/en.json under a custom key. A key that already exists in core is reworded for the whole admin:

{
  "custom": { "nav": { "lessons": "Lessons" } },
  "admin": { "nav": { "users": "Parents" } }
}

The admin has two languages, English and Hindi, and each reads its own file in src/custom/i18n/. Put the words an admin sees in src/custom/i18n/hi.json too. The test suite checks the ones it can tell are admin words, the labels of your admin menu items and admin menu changes and the core admin words you reword, and that hi.json has no key en.json lacks. It also fails on a menu labelKey, admin or member, that no English file has. Words only members see need Hindi only when you offer Hindi to members; anything missing shows in English. See Languages. For a language your members use and the admin does not, such as German, declare it in src/custom/languages.ts and write src/custom/i18n/de.json: see Member languages.

Use core building blocks

The rule forbids editing core files, not using them. Your pages import core components the same way core pages do and get every later fix for free. These are meant for apps:

ComponentImport fromFor
arc-page-headersrc/shared/components/page-header/page-header.component.tsThe title row of every admin and member page
app-global-tablesrc/shared/components/global-table/global-table.component.tsAny list: columns, sorting, empty and loading states, row actions (see its src/shared/components/global-table/README.md)
arc-row-actionssrc/shared/components/row-actions/row-actions.component.tsThe actions of a row in a table or card you build yourself
arc-phone-countrysrc/shared/components/phone-country/phone-country.component.tsThe country beside a phone number box: flag and code, a list when there are several countries ([countries], [(value)], (chosen)). Read the number with chipPhone() and tidy it with tidyChipNumber() from src/shared/utils/phone-country.ts (Sign-in)
arc-country-pickersrc/shared/components/country-picker/country-picker.component.tsA list of countries to choose, with a search by name, ISO id or calling code ([(value)]), as on Settings, SMS. The countries and their codes are in src/shared/data/countries.ts
arc-image-croppersrc/shared/components/image-cropper/image-cropper.component.tsA crop frame over an image ([src], [ratio] as width / height or null for free, (rectChange) in image pixels), as in the media manager. The ratios and the frame geometry are in src/shared/utils/image-crop.ts (Media)
app-user-shellsrc/app/pages/user/user-shell.component.tsThe member area's layout and menu
PwaServicesrc/app/core/pwa/pwa.service.tsWhether a new version of the installable app is waiting (updateReady()), looking for one now (checkForUpdate()) and switching to it (applyUpdate()), for an app that shows the update itself (Make it installable)
StaleCodeServicesrc/app/core/version/stale-code.service.tsWhether this page's code is older than the server's (stale()), for a page that owns its updates and reloads at a safe point with reload() (Make it installable)
AnalyticsServicesrc/app/core/analytics/analytics.service.tsYour own Google Analytics events with log(), sent only when this visitor may be tracked on this page (Analytics). Never import Firebase Analytics directly: a test fails if you do
clearOfflineCache()src/app/core/config/arc-firebase.tsWipes Firestore's offline cache on this device, for a device that changes hands, when the install keeps data offline (Keep working offline)

If a building block falls short, the missing option is added to it in Arc CMS. Copying the component into the custom space loses every later fix.

Why updates never conflict

npm run check:core sorts every file you changed into the custom space (fine), install settings such as src/environments/environment.ts and src/environments/arc-install.ts (fine), dependency lists such as package.json (fine, worth a look), and core (a problem). See Keep core untouched.

In Arc CMS itself, src/app/custom-space.spec.ts holds Arc CMS to this: it fails when a file it shipped in the custom space changed or was deleted, against the hashes in SHIPPED_FILES in scripts/custom-starters.mjs. It is skipped in your app, where the files are yours to change.

Moving a core file into the custom space to change it still counts as a core change: npm run check:core still lists the core file's old path. If you need to change what a core file does, ask for a plug point or a setting in Arc CMS instead.