Member Import Guide
Complete guide for importing legacy members into the ICoTA Members system.
Overview
The member import process consists of three main steps:
- Prepare CSV data - Convert legacy data and chapter names to proper format
- Import users - Load users into Craft CMS
- Import memberships - Create membership records linked to users
Prerequisites
- Legacy member data in CSV format
- Chapter mappings configured in
plugins/icota-members/chapters.json - Craft CMS installed and configured
- Access to command line (terminal)
Step 1: Prepare Import CSV
Script
plugins/icota-members/scripts/prep-import-csv.php
Purpose
- Converts chapter names to UUIDs using
chapters.json - Ensures all users have ICoTA Global chapter assigned
- Creates separate CSV files for user and membership imports
Usage
php plugins/icota-members/scripts/prep-import-csv.php input.csv users-output.csv [membership-output.csv]Arguments:
input.csv- Your source CSV file with legacy member datausers-output.csv- Output file for user importmembership-output.csv- (Optional) Output file for membership import. If not specified, defaults tomembership-import.csv
Input CSV Format
Your source CSV must contain these columns:
Required:
Email- User email addressMembership_Chapter- Chapter name or UUID (can be JSON array or single value)Membership_Expires- Expiry date
Optional:
Status- Membership status (active/expired/cancelled)- Additional user fields (firstName, lastName, etc.)
Example Input CSV:
Email,Membership_Chapter,Membership_Expires,firstName,lastName
john@example.com,USA Chapter,2025-12-31,John,Doe
jane@example.com,"[""European Chapter"", ""ICoTA Global""]",2026-06-30,Jane,Smith
bob@example.com,e3cae15b-9623-4eb8-95db-8f96f1a691a8,2024-12-31,Bob,JonesOutput Files
The script creates two files:
1. Users Output CSV (e.g., users-prepared.csv)
Ready for Craft CMS user import:
- Renames
Membership_Chaptertochapter - Converts chapter names to UUID arrays
- Adds ICoTA Global UUID to all users
- Preserves all other columns
Example Output:
Email,chapter,Membership_Expires,firstName,lastName
john@example.com,"[""e3cae15b-9623-4eb8-95db-8f96f1a691a8"",""eef47251-977c-4035-bb07-9623c8b0b457""]",2025-12-31,John,Doe2. Membership Output CSV (e.g., membership-import.csv)
Ready for membership import:
- Columns:
email,expiryDate,status,source,chapterId - Status automatically determined from expiry date if blank
- Source set to
legacy - ChapterId is the primary (first non-Global) chapter
Example Output:
email,expiryDate,status,source,chapterId
john@example.com,2025-12-31,,legacy,e3cae15b-9623-4eb8-95db-8f96f1a691a8Processing Logic
- Loads chapter mappings from
chapters.json - Finds the chapter column (searches for
Membership_Chapter,chapter, etc.) - For each row:
- Parses chapter data (handles both single values and JSON arrays)
- Converts chapter names to UUIDs
- Adds ICoTA Global UUID if not already present
- Writes user row with UUID array in
chapterfield - Writes membership row with primary chapter (first non-Global, or Global if only option)
Example Run
php plugins/icota-members/scripts/prep-import-csv.php members-export.csv users-prepared.csvOutput:
Loaded chapter mappings:
- USA Chapter
- Asia Chapter
- European Chapter
- Canadian Chapter
- Latin America Chapter
- Middle East & North Africa Chapter
- China Chapter
- ICoTA Global (GLOBAL - will be added to all users)
Found chapter column: 'Membership_Chapter' at index 1
Found email column: 'Email' at index 0
Found expiry column: 'Membership_Expires' at index 2
============================================================
Processing Complete
============================================================
Input file: members-export.csv
Users output: users-prepared.csv
Membership output: membership-import.csv
Total rows: 150
Chapters converted: 150
Chapters not found: 0
ICoTA Global Chapter:
Already had Global: 25
Added Global: 125
✓ Users CSV saved to: users-prepared.csv
✓ Membership CSV saved to: membership-import.csv
Next steps:
1. Import users into Craft CMS using: users-prepared.csv
2. Import memberships using:
ddev craft _icota-members/memberships/import-csv membership-import.csv --skip-errorsTroubleshooting
"Chapter names not found in mapping"
- The script will list any chapter names that don't exist in
chapters.json - Either update
chapters.jsonwith missing chapters, or correct the chapter names in your source CSV
"Could not find 'Membership_Chapter' column"
- The script looks for several variations:
Membership_Chapter,membership_chapter,Chapter,chapter - Ensure your CSV has one of these column names, or rename it
Step 2: Import Users
Using Craft CMS Control Panel
- Navigate to Users in the Craft CMS control panel
- Click Import button
- Upload
users-prepared.csv - Map CSV columns to user fields:
Email→ Emailchapter→ Chapter (custom field)firstName→ First NamelastName→ Last Name- etc.
- Configure import options:
- Set user group if desired
- Enable/disable email notifications
- Click Import
Important Notes
- The
chapterfield must be mapped to the custom Chapter field (Sites field type) - Users will be created with pending status by default
- Duplicate emails will be skipped or updated based on import settings
- Review import preview before confirming
Step 3: Import Memberships
Console Command
_icota-members/memberships/import-csv
Usage
craft _icota-members/memberships/import-csv <csv-path> [--skip-errors]Arguments:
<csv-path>- Path to membership CSV file (created in Step 1)--skip-errors- Continue processing even if some rows fail
CSV Format
Required columns:
email- User email (must match existing Craft user)expiryDate- Membership expiry date
Optional columns:
status- Status (active/expired/cancelled) - auto-determined from expiryDate if blanksource- Source identifier (defaults to "legacy")chapterId- Site UID for chapter assignment
Supported Date Formats
The import command accepts multiple date formats:
2025-12-31(Y-m-d)31/12/2025(d/m/Y)12/31/2025(m/d/Y)31-12-2025(d-m-Y)31st December 2025(jS F Y)31 December 2025(j F Y)December 31, 2025(F j, Y)
All dates are set to 23:59:59 UTC on the specified date.
Auto-Status Determination
If the status column is blank or not provided:
- Past dates: Status set to
expired - Future dates or today: Status set to
active
Duplicate Prevention
The import checks if a user already has a membership from the same source. If found:
- The row is skipped
- A warning is displayed
- No duplicate membership is created
This allows you to safely re-run the import if needed.
Example Import
craft _icota-members/memberships/import-csv /path/to/membership-import.csv --skip-errorsOutput:
Importing legacy members from CSV...
CSV columns detected:
- email (column 1)
- expiryDate (column 2)
- status (column 3)
- source (column 4)
- chapterId (column 5)
✓ Row 2: Imported john@example.com (expires: 2025-12-31, status: active, source: legacy, chapter: e3cae15b-9623-4eb8-95db-8f96f1a691a8)
✓ Row 3: Imported jane@example.com (expires: 2026-06-30, status: active, source: legacy, chapter: 022de685-6eff-408b-b93b-803577ab7c43)
⊗ Row 4: User 'existing@example.com' already has a legacy membership, skipping
✗ Row 5: User not found with email 'missing@example.com'
✓ Row 6: Imported bob@example.com (expires: 2024-12-31, status: expired, source: legacy)
==================================================
Import Summary
==================================================
Successfully imported: 3
Skipped: 2
Errors: 1
==================================================Common Errors
"User not found with email"
- The user doesn't exist in Craft CMS
- Make sure you imported users first (Step 2)
- Check for email address typos
"Invalid expiry date format"
- The date format is not recognized
- Use one of the supported formats listed above
- Common fix: Change
31/12/2025to2025-12-31
"Invalid status"
- Status must be
active,expired, orcancelled - Leave blank to auto-determine from expiry date
Backfill Scripts
After importing users and memberships, you may need to synchronize chapter data.
Backfill User Chapters
Purpose: Assigns chapters to users based on their membership records.
When to use:
- After importing memberships if users don't have chapters assigned
- To sync user chapters with membership chapter data
Script: plugins/icota-members/scripts/backfill-user-chapters.php
Usage:
php plugins/icota-members/scripts/backfill-user-chapters.phpProcess:
- Finds all memberships with
chapterIdset - Collects all chapters for each user
- Validates chapter UIDs are valid sites
- Always ensures ICoTA Global is assigned
- Updates user's
chapterfield with all collected chapters
Example Output:
Found 150 memberships with chapter data
Assigning chapters to users...
Processing 150 users...
✓ User 1 (john@example.com): Assigned to USA Chapter, ICoTA Global
✓ User 2 (jane@example.com): Assigned to European Chapter, ICoTA Global
⚠ User 3: Not found
============================================================
Backfill Complete
============================================================
Updated: 148
Skipped: 2
Total: 150
============================================================Backfill Legacy Chapter
Purpose: Sets chapterId on legacy memberships based on user's chapter field.
When to use:
- After importing users if memberships don't have chapter assignments
- To populate membership chapterId from user chapter data
Script: plugins/icota-members/scripts/backfill-legacy-chapter.php
Usage:
php plugins/icota-members/scripts/backfill-legacy-chapter.phpProcess:
- Finds all legacy memberships without
chapterId - Reads each user's
chapterfield (Sites field) - Selects the first non-Global chapter if available
- Falls back to Global chapter if that's the only assignment
- Updates membership's
chapterId
Example Output:
Found 150 legacy memberships without chapterId
Processing...
✓ Membership 1 (john@example.com): Set chapter to USA Chapter
✓ Membership 2 (jane@example.com): Set chapter to European Chapter
⚠ Membership 3 (bob@example.com): No chapters assigned to user
============================================================
Backfill Complete
============================================================
Updated: 148
Skipped: 2
Total: 150
============================================================Complete Import Workflow
Recommended Order
# 1. Prepare CSV files
php plugins/icota-members/scripts/prep-import-csv.php legacy-members.csv users-to-import.csv memberships-to-import.csv
# 2. Import users via Craft CP
# Navigate to Users → Import, upload users-to-import.csv
# 3. Import memberships
craft _icota-members/memberships/import-csv memberships-to-import.csv --skip-errors
# 4. (Optional) Backfill user chapters from memberships
php plugins/icota-members/scripts/backfill-user-chapters.php
# 5. (Optional) Backfill membership chapters from users
php plugins/icota-members/scripts/backfill-legacy-chapter.phpWhen to Use Backfill Scripts
Use backfill-user-chapters.php when:
- Memberships have chapter assignments, but users don't
- You imported memberships with chapterId, but users don't have the chapter field populated
Use backfill-legacy-chapter.php when:
- Users have chapter assignments, but memberships don't
- You imported users with chapters, but memberships are missing chapterId
Use both when:
- You want to ensure complete synchronization between user chapters and membership chapters
Validation & Testing
Before Full Import
- Test with a small sample (10-20 records)
- Verify chapter mappings in
chapters.json - Check date formats in source CSV
- Ensure email addresses are valid
After Import
- Check import summary for errors and skipped rows
- Review a few user accounts in Craft CP:
- Verify chapter assignments
- Check custom field values
- Query membership records:bash
craft _icota-members/memberships/stats - Check for users without memberships or vice versa
Database Checks
You can verify imports directly in the database:
-- Count users
SELECT COUNT(*) FROM users;
-- Count memberships by status
SELECT status, COUNT(*) FROM icota_members GROUP BY status;
-- Find users without memberships
SELECT u.email FROM users u
LEFT JOIN icota_members m ON m.userId = u.id
WHERE m.id IS NULL;
-- Find memberships without users
SELECT m.id FROM icota_members m
LEFT JOIN users u ON u.id = m.userId
WHERE u.id IS NULL;Best Practices
Data Preparation
Clean your source data first:
- Remove duplicate email addresses
- Standardize date formats
- Verify chapter names match
chapters.json
Backup before importing:
- Database backup
- Keep original CSV files
- Document any data transformations
Start small:
- Import 10-20 test records first
- Verify results before full import
- Use
--skip-errorsfor bulk imports
Chapter Management
Update
chapters.jsonbefore import:- Ensure all chapter names are listed
- Verify UUIDs match Craft site UIDs
- Document any custom chapter mappings
ICoTA Global is automatic:
- Don't manually add it to source data
- The script adds it automatically
- Always validates it's present
Error Handling
Review all warnings and errors:
- Note which rows failed
- Investigate root causes
- Fix and re-import if needed
Use
--skip-errorswisely:- Good for bulk imports with known issues
- Review skipped rows afterward
- Don't use if you need 100% success rate
Check logs:
- Craft logs:
storage/logs/web.log - Import summary output
- Database for incomplete records
- Craft logs:
Troubleshooting
Common Issues
Chapter names not converting to UUIDs:
- Check
chapters.jsonhas the correct chapter names - Verify spelling and capitalization
- Look at the script output for "Chapters not found"
Users imported but memberships fail:
- Ensure users were successfully imported first
- Check email addresses match exactly
- Verify CSV format is correct
Duplicate membership warnings:
- This is expected if re-running the import
- Memberships from the same source won't be duplicated
- Change the
sourcecolumn value to import again
Dates not parsing correctly:
- Check the date format in your CSV
- Use
2025-12-31format for best compatibility - Avoid ambiguous formats like
12/31/25
Chapter field not populating:
- Ensure the
chapterfield exists on users - Verify it's a Sites field type
- Check field mapping during user import
Getting Help
If you encounter issues:
- Review this guide thoroughly
- Check the main documentation (
documentation.md) - Review script output for specific error messages
- Check Craft logs for detailed errors
- Verify database state manually
Appendix
Chapter UUID Reference
From plugins/icota-members/chapters.json:
| UUID | Chapter Name |
|---|---|
e3cae15b-9623-4eb8-95db-8f96f1a691a8 | USA Chapter |
19cd36d2-fa3e-4a3f-9dd3-490fa27d5df0 | Asia Chapter |
022de685-6eff-408b-b93b-803577ab7c43 | European Chapter |
9e668174-c8fa-432e-beba-0a87d7f9e988 | Canadian Chapter |
eae60906-eb70-43a5-a64a-7b1ab761ab13 | Latin America Chapter |
aa3b0b2c-6d8e-4b99-bb90-852cbbe629f7 | Middle East & North Africa Chapter |
bda224ee-134c-4b40-9f78-55b152f80927 | China Chapter |
eef47251-977c-4035-bb07-9623c8b0b457 | ICoTA Global (auto-assigned) |
File Locations
- Prep script:
plugins/icota-members/scripts/prep-import-csv.php - Backfill user chapters:
plugins/icota-members/scripts/backfill-user-chapters.php - Backfill membership chapters:
plugins/icota-members/scripts/backfill-legacy-chapter.php - Chapter mappings:
plugins/icota-members/chapters.json - Console controller:
plugins/icota-members/src/console/controllers/MembershipsController.php
Related Documentation
- Main plugin documentation:
documentation.md - Developer context:
plugins/icota-members/CLAUDE.md - Craft CMS user import: Craft CMS Documentation