Files
WebForm-mw/PRODUCTION_CUTOVER.md
T
Joel Brock 9341f67231 Add PRODUCTION_CUTOVER.md and link from DEPLOYMENT.md
Cross-references the Civi-side cutover checklist from the deploy doc
so anyone deploying knows the CiviCRM prerequisites are tracked in a
single place.
2026-06-09 12:09:00 -07:00

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_KEY from civicrm.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:

  • Inquiry
  • Organizing
  • Feasibility
  • Business feasibility
  • Store Implementation
  • Stabilize 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.

  1. Copy WebForm-mw/civi-extension/webform-mw/ to the CRM's [civicrm.extensionsDir] (usually <civi-root>/sites/default/ext/). The directory must be named exactly webform-mw (matches <key> in info.xml).

  2. Administer → System Settings → Extensions → Add new → Refresh, then Install next to "WebForm-mw".

  3. 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_KEY must equal the STAFF_REPORT_KEY env var on the Next.js app. Rotate them together.

  4. 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 the WEBFORM_MW_STAFF_KEY in civicrm.settings.php (step 5).
  • HEALTH_TOKEN — set this in production, or /api/health returns 404. Use openssl rand -hex 32.
  • Confirm none of the three core CIVI_* vars are missing — the app refuses to start in NODE_ENV=production if any are unset.

8. Post-deploy verification

Run these against the production deploy:

  1. curl https://survey.fci.coop/healthz{"ok":true,"service":"coop-checkin"}

  2. curl 'https://survey.fci.coop/api/health?token=$HEALTH_TOKEN' → all checks green. This validates Civi connectivity, the Primary Contact relationship type, the Check-in (organizing) activity type, all six custom groups, the Stage option group, and the option-group ID match.

  3. Pick a test Individual contact that has a Primary Contact relationship 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.

  4. 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"]'
    
    • Stage must be null (not "Unknown"). If it's "Unknown", step 2 above wasn't completed on this CRM.
    • subject should be Co-op Survey (form submission).
  5. 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 via CustomField.update; see step 2.