Cross-references the Civi-side cutover checklist from the deploy doc so anyone deploying knows the CiviCRM prerequisites are tracked in a single place.
9.4 KiB
Production cutover guide
Step-by-step CiviCRM-side checklist to recreate the Co-op Survey
environment on a new CiviCRM (e.g. when moving from
client.crm.fci.coop to production crm.fci.coop). The Next.js app
deploy is covered separately in DEPLOYMENT.md (Render)
and AMPLIFY_DEPLOY.md (AWS Amplify). This
document captures everything that must exist inside CiviCRM for those
deployments to work.
Keep this file current — every time we discover a manual Civi-side prerequisite, add it here so the next environment cutover is a single read-through.
0. Prerequisites on the target CiviCRM
- CiviCRM 5.50+ on PHP 7.4 or 8.x.
- A service-user contact with an API key generated (Contact view → edit → API Key field). The same user's permissions are what the app runs as.
- The site's
CIVICRM_SITE_KEYfromcivicrm.settings.php. - If CiviCRM sits behind webserver-level Basic Auth, credentials for it.
These three (CIVI_BASE_URL, CIVI_API_KEY, CIVI_SITE_KEY) are the
core env vars the app needs.
1. Activity type and custom field groups
The form writes one activity per submission, of type
Check-in (organizing) (machine name, exact spelling).
That activity type must exist with the six custom field groups
attached, all extending Activity filtered to this single activity
type:
| Custom group machine name | Stage |
|---|---|
Check_in_data__organizing_ |
0 (always-on survey data + the Stage snapshot field) |
Stage_1 |
1 — Convene & Prepare |
Stage_2 |
2 — Grow & Plan |
Stage_3 |
3 — Connect & Gather |
Stage_4 |
4 — Excite & Build |
Stage_5 |
5 — Fulfill & Stabilize |
Field names within those groups are referenced explicitly by
config/form.ts (search for civiField:). If you renamed any field on
the source CRM, mirror the change here or update the config.
Sanity check after import: Activity → Search for any existing
Check-in (organizing) row and confirm every custom field renders.
2. The Stage custom field default — must be cleared
Custom field id 242 (label "Stage", inside the
Check_in_data__organizing_ group) must have an empty default
value. The form is intentionally not the authority on stage; staff
set Stage on their own stage-change check-ins, and /api/data derives
the org's current stage from the most recent stage-bearing activity.
If a default is set (the original config had "Unknown" as the default), every form-submitted activity gets stamped with that value and contaminates the stage-derivation logic.
The Civi admin UI's dropdown does not allow you to select "blank", so clear it via API or SQL:
Option A — API (preferred):
cv api4 CustomField.update \
where='[["id","=",242]]' \
values='{"default_value":null}'
Or in the API Explorer v4 (Support → Developer → API Explorer v4):
Entity: CustomField
Action: update
where: [["id","=",242]]
values: {"default_value": null}
Option B — direct SQL (only if the API isn't handy):
UPDATE civicrm_custom_field SET default_value = NULL WHERE id = 242;
Then cv flush or Administer → System Settings → Cleanup Caches.
Cleanup of historical bad data (optional, only if production was running with the bad default for a while):
cv api4 Activity.update \
where='[["activity_type_id:name","=","Check-in (organizing)"],
["Check_in_data__organizing_.Stage","=","Unknown"]]' \
values='{"Check_in_data__organizing_.Stage": null}'
Verify: pull one activity back and confirm Stage is null:
cv api4 Activity.get \
where='[["activity_type_id:name","=","Check-in (organizing)"]]' \
select='["id","Check_in_data__organizing_.Stage"]' \
limit=5
3. Framework Stage option group on Organizations
Each Organization contact has a custom field that holds its current
Framework Stage. The option group backing it (referenced by
STAGE_OPTION_GROUP_ID in config/form.ts, currently 75) must
contain these six values, spelled exactly:
InquiryOrganizingFeasibilityBusiness feasibilityStore ImplementationStabilize newly opened co-op
If the option group id differs in the target CRM, update
STAGE_OPTION_GROUP_ID in config/form.ts and redeploy.
4. Relationship type: form-filler → organization
The form resolves "what org is this submission about?" by looking up
the form-filler's Primary Contact relationship to an
Organization. The relationship type must exist with:
name_a_b=Primary Contact- side A = Individual
- side B = Organization
This is the value of FORM_CONTACT_RELATIONSHIP in config/form.ts.
If you use a different relationship type in the target CRM, update that
constant and redeploy.
Per-org setup (this is the recurring operational step): for each
Organization that should receive a survey, create exactly one active
Primary Contact relationship from the staff/board contact who will
fill out the form to that Organization.
- Zero active relationships → the form returns
No active "Primary Contact" relationship found for your contact. - More than one active relationship → the form refuses with
multiple active … relationships; staff must resolve before this link will work.(Deactivate the older ones.)
5. Install the webform-mw Civi extension
The extension adds an Engagement Report tab to Organization contact pages that embeds the staff report in an iframe.
-
Copy
WebForm-mw/civi-extension/webform-mw/to the CRM's[civicrm.extensionsDir](usually<civi-root>/sites/default/ext/). The directory must be named exactlywebform-mw(matches<key>ininfo.xml). -
Administer → System Settings → Extensions → Add new → Refresh, then Install next to "WebForm-mw". -
Configure — add to
civicrm.settings.php:define('WEBFORM_MW_APP_URL', 'https://survey.fci.coop'); define('WEBFORM_MW_STAFF_KEY', '...same value as STAFF_REPORT_KEY in the app env...');WEBFORM_MW_STAFF_KEYmust equal theSTAFF_REPORT_KEYenv var on the Next.js app. Rotate them together. -
Confirm: open any Organization contact in CiviCRM — there should be an Engagement Report tab. Click it; the staff report should load with no nested scrollbar (the extension auto-sizes the iframe via
postMessage).
Full extension docs:
civi-extension/webform-mw/README.md.
6. CSP / frame-ancestors — app side
The Next.js app's /staff/report route must allow the CiviCRM origin
in its frame-ancestors CSP, or the iframe will refuse to render.
The build reads CIVI_BASE_URL and adds its origin to the CSP
automatically — so make sure CIVI_BASE_URL on the app deploy points
at the production CRM origin (https://crm.fci.coop), not
client.crm.fci.coop.
Confirm after deploy:
curl -sI https://survey.fci.coop/staff/report | grep -i content-security-policy
Should include frame-ancestors 'self' https://crm.fci.coop (or
whatever your production CRM origin is).
7. App env vars (cross-reference)
The full list lives in DEPLOYMENT.md.
Production-specific reminders:
CIVI_BASE_URL— production CRM, not the client/staging one.STAFF_REPORT_KEY— long random secret, identical to theWEBFORM_MW_STAFF_KEYincivicrm.settings.php(step 5).HEALTH_TOKEN— set this in production, or/api/healthreturns 404. Useopenssl rand -hex 32.- Confirm none of the three core
CIVI_*vars are missing — the app refuses to start inNODE_ENV=productionif any are unset.
8. Post-deploy verification
Run these against the production deploy:
-
curl https://survey.fci.coop/healthz→{"ok":true,"service":"coop-checkin"} -
curl 'https://survey.fci.coop/api/health?token=$HEALTH_TOKEN'→ all checks green. This validates Civi connectivity, thePrimary Contactrelationship type, theCheck-in (organizing)activity type, all six custom groups, the Stage option group, and the option-group ID match. -
Pick a test Individual contact that has a
Primary Contactrelationship to a test Organization. Generate a Civi checksum for that Individual and load:https://survey.fci.coop/?cid=<id>&cs=<checksum>— the form should load with prefill. -
Submit a stub answer. Then in Civi, pull the new activity:
cv api4 Activity.get \ where='[["activity_type_id:name","=","Check-in (organizing)"]]' \ orderBy='{"id":"DESC"}' limit=1 \ select='["id","subject","Check_in_data__organizing_.Stage"]'Stagemust be null (not "Unknown"). If it's "Unknown", step 2 above wasn't completed on this CRM.subjectshould beCo-op Survey (form submission).
-
Open the target Organization in CiviCRM → Engagement Report tab. The staff report should render the submission you just made.
Change log
When you discover a new manual prerequisite, append a short note here so the rationale survives.
- 2026-06-08 — Documented the field-242 "Unknown" default issue
after a production submission was stamped
Stage = "Unknown". Cleared viaCustomField.update; see step 2.