Cloud Functions

Every Cloud Function Arc CMS deploys, grouped by area: what kind it is, who may call it or what starts it, and what it does. Use it to find the function behind a screen, or the one to deploy after a change.

How a function is named

functions/src/index.ts exports every function in one group, arccms. A function exported as sendTestEmail therefore deploys as arccms-sendTestEmail. The rule is arcFunctionName() in functions/src/function-names.ts, and the functions belong to the arccms codebase in firebase.json.

To deploy one function instead of all of them, see Deploy only what changed.

Which functions are deployed

Core functions are listed in functions/src/all.ts and always deploy. A switchable feature's functions are listed in functions/src/features/, one file per feature. Before every functions build, scripts/arc-features.mjs reads src/custom/features.ts and writes a generated list of the feature files whose features are on, which functions/src/all.ts exports.

The feature ids are in Feature ids.

Who may call a function

Each callable checks its caller at the top of its handler. The tables below use these words:

WordMeaning
AdminThe caller's arccms_role claim is admin (functions/src/users/claims.ts). Some functions also read the claim from the account itself (functions/src/search/auth.ts), so a fresh claim works before the token refreshes.
Signed inAny signed-in account, whatever its role.
Own recordSigned in, with a users record of their own (requireOwnRecord() in functions/src/auth/accounts.ts), and not a locked app account (App accounts). Only refreshMyClaims also takes a locked app account.
PublicNo sign-in needed. Where a limit is given, it counts per caller (a hash of the IP address) or per address, in a fixed hour.

Roles and claims are explained in Roles and claims.

Every Firestore trigger listens on the install's own database, ARC_DATABASE_ID (arcDocument() in functions/src/arc-config.ts). Every schedule runs in UTC.

A trigger's database is fixed when it is deployed: after changing the database, deploy the functions again (see Configuration keys).

Content

Owned by Content. Deployed only when the content feature is on.

FunctionKindWho or what starts itWhat it does
processPublishQueueFirestore trigger: _publish_queue/{queueId} createdThe admin app, on publish, unpublish, update, delete or a new entry orderCopies the draft to its published collection and releases the static pages to Firebase Hosting, with the home page when it shows that type's cards, and every page when a standard page's footer link changes; with seo on, also the sitemap, feeds and discoverability files, and tells IndexNow.
onContentTypeDeletedFirestore trigger: ContentTypes/{contentTypeId} deletedDeleting a content typeDeletes the type's published, draft and Tags_{slug} collections, with each entry's translations, PublishedHistory and DeploymentLogs, and their search entries when search is on. Deletes nothing when another content type still uses the same slug.
onSiteSettingsWrittenFirestore trigger: Settings/{settingId} writtenAn admin saving Settings, and the setup wizardWhen about, site, localization or misc changed, queues a republish of the home page (a home item on _publish_queue), or of the whole site (redeploy-all) when About changed and other site files print it with data-arc-site.
seedStaticPagesCallableIntended for deploy tooling; npm run seed:dev and seed:prod run the same code directlyPublishes the home page in every language, every published content page and the static pages to Firebase Hosting as static HTML.

Search

Owned by Search. Deployed only when the search feature is on; onSearchQueued also needs content.

FunctionKindWho or what starts itWhat it does
searchCallablePublic for the public scope; signed in for the authenticated scope; admin for the admin scopeRuns a search and returns the results. Every search box uses it.
reindexSearchCallableAdminRebuilds the index for every source, one source or one collection.
listSearchCollectionsCallableAdminLists the database's collections and their search setup, for Search settings.
sampleCollectionFieldsCallableAdminSamples up to 20 documents of a collection and returns their text fields, for setting it up.
onSearchQueuedFirestore trigger: _search_queue/{id} writtenThe browser, on each draft saveIndexes the named draft, then deletes the queue entry.

Search also deploys one trigger per collection made searchable in functions/src/custom/search-sources.ts, as the group searchSync: a collection Products deploys as arccms-searchSync-Products (functions/src/search/collectionTriggers.ts). Each keeps its collection's search entries in step with its documents.

SEO

Owned by SEO and discoverability. Deployed only when the seo feature is on.

FunctionKindWho or what starts itWhat it does
regenerateSeoFilesCallableAdminReleases robots.txt, llms.txt and the IndexNow key file to Firebase Hosting right away.

Signup forms

Owned by Signup forms. Deployed only when the forms feature is on (which needs audience). In the code a form is a Waitlists document and its members are Waitlists/{id}/users.

FunctionKindWho or what starts itWhat it does
ensureWaitlistExistsCallableThe public signup formCreates the form document with defaults when no form has that id or slug.
joinFormCallablePublicFinds or creates the member for an email and returns the member id.
requestFormOtpCallablePublic, one code a minute per form and addressEmails the form's verification code.
verifyFormOtpCallablePublicChecks a form's code against its expiry and attempt limit.
finalizeFormSignupCallablePublic; needs a verified code when the form sends oneCompletes the signup: marks the member verified and confirmed and updates the form's signup count.
onWaitlistsCreateFirestore trigger: Waitlists/{waitlistsId} createdA new formCreates the form's audience list and its default code and welcome emails.
onWaitlistsUpdateFirestore trigger: Waitlists/{waitlistsId} updatedA form changeRenames the form's audience list when the form's name changes.
onWaitlistsDeleteFirestore trigger: Waitlists/{WaitlistsId} deletedDeleting a formDeletes the form's subcollections and its audience list.
onWaitlistUserCreateFirestore trigger: Waitlists/{WaitlistsId}/users/{usersId} createdA new memberSends the welcome email to a member who joined directly and is already verified.
onWaitlistUserUpdateFirestore trigger: Waitlists/{WaitlistsId}/users/{usersId} updatedA member changeSends the welcome email when the member's email becomes verified, unless a sequence sends it.
syncOtpEnabledFlagFirestore trigger: EmailTemplate/{templateId} writtenA template changeCopies whether a form's code email is active to the form's otpEnabled.
getWaitlistTemplateDefaultsCallableAdminReturns the default code and welcome emails, without writing.
backfillWaitlistTemplatesCallableAdminCreates missing default emails for existing forms, and gives a welcome that is still the old default with referral and leaderboard buttons the plain one. dryRun only reports the missing emails.
normalizeWaitlistTemplateIdsCallableAdminMerges duplicate form emails onto one document per form and type. dryRun only reports.
stampFormTargetListsCallableAdminSets targetListIds on existing forms that lack it. dryRun only reports.

Signup forms have no referrals or leaderboard, so these functions of older versions are gone: creditReferral, getPublicLeaderboard, getPublicMemberView, getOptimizedLeaderboard, migrateWaitlistedUsers, onReferralCreate, onReferralUpdate and onWaitlistUserDelete. A deploy of all the functions deletes them from a project that still has them (see Functions that are deleted).

Audience

Owned by Audience. Deployed only when the audience feature is on. Contacts are written only by functions, so every admin change to a contact goes through a callable.

FunctionKindWho or what starts itWhat it does
onUserCreateContactFirestore trigger: users/{docId} createdA new userCreates or updates the person's contact and adds it to the all-users list.
onUserDeleteContactFirestore trigger: users/{docId} deletedDeleting a userUnlinks the contact from the user and drops it from the system lists.
onWaitlistUserCreateContactFirestore trigger: Waitlists/{waitlistId}/users/{userId} createdA form signupCreates the member's contact, usually as pending, and adds it to the form's lists.
onWaitlistVerifiedContactFirestore trigger: Waitlists/{waitlistId}/users/{userId} updatedA member verifying or being confirmedMakes the pending contact subscribed.
onWaitlistUserDeletedFirestore trigger: Waitlists/{waitlistId}/users/{userId} deletedDeleting a memberRemoves the contact from the form's own list. The contact itself stays.
onContactTagDeleteFirestore trigger: ContactTags/{tagId} deletedDeleting a tagRemoves the tag from every contact.
adminAddContactCallableAdminAdds or updates one contact and its lists.
adminUpdateContactListsCallableAdminAdds a contact to lists or removes it from them.
adminSetContactConsentCallableAdminSets a contact's marketing consent and keeps the Suppression list in step.
adminSetContactDisabledCallableAdminTurns every email to a contact off, or back on.
adminDeleteContactCallableAdmin, with confirm: trueErases a contact everywhere and keeps a record with only its hash in ErasureLog.
adminSetContactTagsCallableAdminSets a contact's tags.
adminUpsertContactFieldCallableAdminCreates or updates a custom contact field.
adminDeleteContactFieldCallableAdminDeletes a field definition. Values on contacts stay.
adminSetContactFieldsCallableAdminSets field values on one contact.
previewContactImportCallableAdminParses a CSV and returns its valid, invalid and duplicate rows, without writing.
importContactsCallableAdminImports parsed rows into a list, as subscribed only when the admin confirms consent.
backfillContactsCallableAdminBuilds contacts from existing users and verified form members. dryRun only reports.
backfillPendingContactsCallableAdminGives unverified form signups a pending contact. dryRun only reports.
backfillFormListsCallableAdminCreates or repairs each form's audience list. dryRun only reports.
migrateTagsToContactsCallableAdminCopies per-form member tags onto contacts as shared tags. dryRun only reports.
migrateFormDataToContactFieldsCallableAdminCopies stored form answers onto contact fields through each form's field map. dryRun only reports.

App audience

Owned by App audience, part of the audience feature and deployed with it. The host app's users are where ARC_APP_USERS_PATH and ARC_APP_USERS_DATABASE point (functions/src/app-audience/config.ts).

FunctionKindWho or what starts itWhat it does
appAudienceStatusCallableAdminReturns where the host app's users are and the saved App audience settings.
sampleAppUsersCallableAdminLists the fields found in a sample of host user documents.
testAppUserCallableAdminReads one host user with the given settings, before they are saved.
listAppUsersCallableAdminLists the app users, with an optional search.
getAppUserCallableAdminReturns one app user's fields, state and recent events.
previewAppListCallableAdminReturns who a live App users list matches now: counts and the first 200.
onAppUserWrittenFirestore trigger: the host app's user documents, written; retriedThe host app changing a userEmits app events and joins or leaves sequences as the user changes. It does nothing until the path is set.

Email engine

Owned by Email engine. Always deployed.

FunctionKindWho or what starts itWhat it does
onEmailLogCreateFirestore trigger: EmailLogs/{EmailLogsId} createdEvery queued emailSends a pending email through the active mail provider. A sendNow email, such as a sign-up code, is sent by the call that queued it, and this sends it only if that call did not.
retryPendingEmailsScheduled: every 5 minutesThe scheduleSends again the emails in retrying or deferred whose next attempt is due.
handleUnsubscribeHTTP, GET and POSTPublic, with a signed linkUnsubscribes the recipient. GET shows a page; POST is one-click unsubscribe.
handleEmailPreferencesHTTPPublic, with a signed linkShows the recipient's preferences page and subscribes or unsubscribes them.
unsubscribeLegacyLinkCallableThe unsubscribe page, for an older linkUnsubscribes the recipient of an older unsubscribe link, one that carries a member id instead of a signed token.
handleEmailWebhookHTTPThe mail providerRecords sent, delivered, bounce, complaint and reject events on the email log.
trackEmailOpenHTTPAn email client loading the tracking pixelRecords an open and returns a 1x1 GIF.
sendTestEmailCallableAdminSends composed content as a test email.
seedEmailTemplatesCallableAdminCreates missing default templates, notification types and event mappings, and the system lists when audience is on.
dedupeEmailTemplatesCallableAdminDeletes duplicate templates of the same type and form.
purgeEmailLogsCallableAdminDeletes email logs older than a number of days (60 by default).
scheduledPurgeEmailLogsScheduled: every day 02:00 UTCThe scheduleDeletes email logs older than the retention in the email settings (60 days by default).
testProviderConnectionCallableThe Email settings pageTests a mail provider configuration.
testSmtpConfigConnectionCallableThe Email settings pageTests a mail provider configuration and sends a test email with it.

Email marketing

Owned by Email marketing. Deployed only when the email-marketing feature is on (which needs audience).

FunctionKindWho or what starts itWhat it does
processBroadcastFirestore trigger: BroadcastEmails/{broadcastId} createdA new broadcastQueues one email per recipient of a queued broadcast.
continueBroadcastFirestore trigger: _broadcast_continue/{docId} createdA broadcast that ran out of time, or a scheduled one startingResumes the broadcast where it stopped.
processScheduledBroadcastsScheduled: every 5 minutesThe scheduleStarts scheduled broadcasts that are due, and marks those more than a day late as failed.
previewBroadcastAudienceCallableAdminCounts who a broadcast would reach, respecting consent.
processDripQueueScheduled: every 15 minutesThe scheduleSends the due step of each active sequence enrollment.
activateDripCampaignCallableAdminActivates a sequence, and enrolls the list's current members when it is set to.
archiveDripCampaignCallableAdminArchives a sequence and ends its enrollments.
migrateWelcomeToSequencesCallableAdminTurns each form's welcome email into the first step of a sequence. dryRun only reports.
sendAnnouncementCallableAdminSends an announcement to an audience as in-app notifications, and by email when asked.

Automations

Owned by Automations. Always deployed.

FunctionKindWho or what starts itWhat it does
onAppEventCreateFirestore trigger: AppEvents/{id} createdAny code that emits an app eventRuns the event's mapping from Settings/event_mappings: a notification, an email or a list change.

Notifications

Owned by Notifications. Always deployed.

FunctionKindWho or what starts itWhat it does
onNotificationCreateFirestore trigger: Notifications/{id} createdA new notificationDecides whether the notification is also emailed, and records why when it is not.
sendAdminDigestScheduled: every hourThe schedule; sends only in the configured hourEmails every admin a 24-hour summary when the digest is on.
getMyNotificationPrefsCallableSigned inReturns the notification types a person can choose and their email choices.
updateMyNotificationPrefsCallableSigned inSaves the person's email choice for each notification type.

Sign-in

Owned by Sign-in methods. Email and Google sign-in, and adding an email or number to an account, are always deployed.

FunctionKindWho or what starts itWhat it does
checkEmailAccountCallablePublic, 100 an hour per callerSays whether an email is registered, new, an unfinished sign-up, or has no access.
requestSignupOtpCallablePublic, 20 an hour per caller and 5 an hour per addressEmails a sign-up code. With the Debug Provider the reply also carries the code, for the sign-up page to show. Asked again inside the minute, it sends nothing and answers alreadySent with the seconds left.
verifySignupOtpCallablePublic; own record for a link codeChecks a sign-up, reset or link code and returns a ticket.
createAccountRecordCallableSigned in with email and passwordCreates the person's users record with the default role and sets the claims. Refuses a locked app account a password was linked to, and takes the password off it.
requestPasswordResetCallablePublic, 20 an hour per caller and 5 an hour per addressForgot password: emails a reset code to an account this site may reset. sent: false when the email engine sent nothing, and the page has Firebase email its reset link. With the Debug Provider the reply carries the code only when Show password reset codes on screen is on.
resetPasswordCallablePublic, with the ticket from a checked reset codeChecks the new password against the password rule, sets it, uses the code up and signs out every other session.
ensureGoogleAccountCallableSigned in with GoogleCreates a users record for a new Google sign-in, when Google sign-in and sign-ups are on. Refuses a locked app account Google was linked to, takes Google off it and ends its sessions.
checkIdentifierForLinkCallableOwn recordSays whether an email or number is free, already theirs, or on another account.
requestEmailLinkOtpCallableOwn record, 10 an hourEmails a code to an address being added.
linkEmailCallableOwn recordAdds the verified address to the account, moving it from another account if needed. A first password must meet the password rule (reason weak-password).
linkPhoneCallableOwn record; phone sign-in onAdds the verified number to the account, moving it from another account if needed.

Phone sign-in

Phone sign-in cannot work without an SMS provider, so these belong to the SMS feature and deploy only when sms is on. Each also refuses to run unless phone sign-in is turned on in the sign-in settings.

FunctionKindWho or what starts itWhat it does
checkPhoneAccountCallablePublic, 100 an hour per callerSays whether a number has an account and a PIN.
requestPhoneOtpCallablePublic, 20 an hour per caller; own record to add a numberTexts a code for sign-up, a PIN reset, or adding the number to an account. Asked again for the same purpose inside the minute, it sends nothing and answers alreadySent with the seconds left; a code for another purpose goes at once.
verifyPhoneOtpCallablePublic; own record for a link codeChecks a text code and returns a ticket.
completePhoneSignupCallablePublic, with a verified ticketCreates the sign-in account and users record for a number, with its PIN.
signInWithPinCallablePublic, 30 an hour per callerChecks the PIN and returns a sign-in token.
resetPinCallablePublic, with a verified ticketSets a new PIN, signs out every other session and returns a sign-in token.
setPinCallableOwn record with a phone numberSets the account's PIN.

SMS

Owned by SMS. Deployed only when the sms feature is on.

FunctionKindWho or what starts itWhat it does
sendTestSmsCallableAdminTexts a test code to a number through the configured provider.
checkPhoneSignInCallableAdminSigns a throwaway sign-in token to check that phone sign-in can sign people in, and returns the fix for this project when it cannot.

Users and roles

Owned by Users and roles. Always deployed. What deleting an account removes is set out in Follow the account contract.

FunctionKindWho or what starts itWhat it does
onUserCreatedFirestore trigger: users/{docId} createdA new userAdds the hashed email to email_lookup, emits user.signed_up and tells the admins.
onUserCreateWelcomeEmailFirestore trigger: users/{docId} createdA new user with an emailQueues the sign-up welcome email.
onUserRoleChangeFirestore trigger: users/{docId} writtenA user record created or changedSets the arccms_role and arccms_uid claims from the record. A blocked or detached record loses them and its sessions end.
onUserDeletedFirestore trigger: users/{docId} deleted; retried for up to an hourDeleting a userEnds access, deletes the sign-in (unless another app owns it), the lookups, PIN, app PINs and phone PIN gate counts, subcollections, Storage folders, feedback and notifications, then emits user.deleted.
onSignInDeletedAuth trigger (first generation): a sign-in account deletedA sign-in deleted outside Arc CMS, such as by the person's browserWhen a users record still points at the sign-in, keeps it and emits user.signInDeleted. Arc CMS's own deletions remove the record first, so they emit nothing.
claimFirstAdminCallableSigned in with a users record that is not a locked app account, only while no admin has ever existedMakes the caller the site's first admin. Called again by that admin, it restores the role if it was lost before setup finished.
syncAllUserRolesCallableAdmin, by the role on their users recordApplies every user's claims again from their records.
fillAccountSourcesCallableAdmin, by the claimGives every users record with no by the value unknown, so the users list's People filter finds it. Called by the users page itself, once, when its counts show such records.
adminCreateUserCallableAdminCreates a sign-in account and users record with a role, or reuses an existing sign-in. A new account's temporary password must meet the password rule.
adminSetPasswordCallableAdminSets a person's password on their sign-in account, never in Firestore, after the password rule (refused with the reason weak-password and the problem). Refuses a sign-in another app owns or shares (shared-account), a locked app account (app-managed), an account with no email (no-email) and a record whose sign-in account is missing (no-account).
refreshMyClaimsCallableSigned in with a users record, a locked app account tooApplies the caller's claims again. A blocked account loses them instead. A locked app account signed in without the app's token is refused, loses that way in and is signed out everywhere.
deleteMyAccountCallableOwn record, signed in within the last 10 minutes, not an admin, not a locked app accountErases the person's contact and deletes their record; onUserDeleted does the rest.

Payments

Owned by Payments. Deployed only when the payments feature is on. The provider is Dodo Payments.

FunctionKindWho or what starts itWhat it does
createCheckoutSessionCallableSigned inCreates a checkout for a product at its current price tier and returns the checkout URL.
createTestCheckoutLinkCallableAdminCreates a test checkout URL for one tier's discount code.
dodoWebhookHTTPDodo Payments, with a verified webhook signatureSaves the raw event to WebhookEvents, once per webhook id.
handlePaymentEventFirestore trigger: WebhookEvents/{eventId} created; retriedA saved webhook eventUpdates the transaction, the purchase count and the user's entitlement, and sends the payment email.
testDodoConnectionCallableAdminChecks that the stored Dodo Payments keys work.
consumeCreditsCallableSigned inSpends the caller's prepaid credits and records it in CreditLedger.

Entitlement reminders and expiry

Part of the payments feature and deployed with it. See Premium entitlements.

FunctionKindWho or what starts itWhat it does
scanExpiredEntitlementsScheduled: every day 08:00 UTCThe scheduleEnds subscriptions past their expiry plus a grace period, in case a webhook was missed.
scanTrialEndingsScheduled: every day 09:00 UTCThe scheduleEmails, once, each user whose trial ends soon.
scanUpdatesEndingScheduled: every day 09:30 UTCThe scheduleEmails, once, each user whose free updates end soon.

Feedback

Owned by Feedback button. Always deployed.

FunctionKindWho or what starts itWhat it does
onFeedbackCreatedFirestore trigger: Feedback/{id} createdA person sending feedbackAdds the sender's name, email and phone from their record, and notifies the admins, up to 10 alerts an hour per sender.

Contact form

Owned by Contact form. Deployed with the contact feature.

FunctionKindWho or what starts itWhat it does
submitContactMessageCallable, publicA contact form on a published page (arc-site.js)Checks the message (honeypot, time on the page, five an hour per sender address and per email, field lengths) and stores it in ContactMessages; one with many links is kept as possible spam.
onContactMessageCreatedFirestore trigger: ContactMessages/{id} createdA stored contact messageNotifies the admins (admin_contact_message), up to 10 alerts an hour; possible spam raises none.

Analytics

Owned by Analytics. Deployed only when the analytics feature is on.

FunctionKindWho or what starts itWhat it does
connectGoogleAnalyticsCallableAdminExchanges a Google authorization code and saves the connection.
selectAnalyticsPropertyCallableAdminSaves the chosen Google Analytics property.
refreshAnalyticsDataCallableAdminFetches fresh data from Google Analytics and stores it.
disconnectGoogleAnalyticsCallableAdminRevokes the Google token and removes the connection.
testAnalyticsConnectionCallableNot used by the admin screensChecks the service account settings and returns the property's details.

Media

Owned by Media. Always deployed.

FunctionKindWho or what starts itWhat it does
searchUnsplashCallableThe media manager, signed inSearches Unsplash images with the key kept on the server.
readMediaImageCallableThe media manager's Save cropped copy, admins onlyReturns an upload's XL file as bytes, so the browser can crop it whatever the bucket's CORS setup.
readMediaFileCallableThe media manager's Take an image on a library PDF, admins onlyReturns the PDF in 6 MB parts (base64, with the offset and total), so the browser can draw its pages whatever the bucket's CORS setup. PDFs up to 20 MB, from the mediaFiles document only.

Installable app

Owned by Installable app (PWA). Deployed only when the pwa feature is on, which it is not by default.

FunctionKindWho or what starts itWhat it does
trackPwaEventCallablePublic, 30 an hour per callerCounts an install prompt, install, dismissal or installed open per day and platform, and marks a signed-in person's record as installed.