Skip to content

Member Import Guide

Complete guide for importing legacy members into the ICoTA Members system.

Overview

The member import process consists of three main steps:

  1. Prepare CSV data - Convert legacy data and chapter names to proper format
  2. Import users - Load users into Craft CMS
  3. 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

bash
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 data
  • users-output.csv - Output file for user import
  • membership-output.csv - (Optional) Output file for membership import. If not specified, defaults to membership-import.csv

Input CSV Format

Your source CSV must contain these columns:

Required:

  • Email - User email address
  • Membership_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:

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,Jones

Output Files

The script creates two files:

1. Users Output CSV (e.g., users-prepared.csv)

Ready for Craft CMS user import:

  • Renames Membership_Chapter to chapter
  • Converts chapter names to UUID arrays
  • Adds ICoTA Global UUID to all users
  • Preserves all other columns

Example Output:

csv
Email,chapter,Membership_Expires,firstName,lastName
john@example.com,"[""e3cae15b-9623-4eb8-95db-8f96f1a691a8"",""eef47251-977c-4035-bb07-9623c8b0b457""]",2025-12-31,John,Doe

2. 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:

csv
email,expiryDate,status,source,chapterId
john@example.com,2025-12-31,,legacy,e3cae15b-9623-4eb8-95db-8f96f1a691a8

Processing Logic

  1. Loads chapter mappings from chapters.json
  2. Finds the chapter column (searches for Membership_Chapter, chapter, etc.)
  3. 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 chapter field
    • Writes membership row with primary chapter (first non-Global, or Global if only option)

Example Run

bash
php plugins/icota-members/scripts/prep-import-csv.php members-export.csv users-prepared.csv

Output:

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-errors

Troubleshooting

"Chapter names not found in mapping"

  • The script will list any chapter names that don't exist in chapters.json
  • Either update chapters.json with 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

  1. Navigate to Users in the Craft CMS control panel
  2. Click Import button
  3. Upload users-prepared.csv
  4. Map CSV columns to user fields:
    • Email → Email
    • chapter → Chapter (custom field)
    • firstName → First Name
    • lastName → Last Name
    • etc.
  5. Configure import options:
    • Set user group if desired
    • Enable/disable email notifications
  6. Click Import

Important Notes

  • The chapter field 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

bash
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 blank
  • source - 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

bash
craft _icota-members/memberships/import-csv /path/to/membership-import.csv --skip-errors

Output:

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/2025 to 2025-12-31

"Invalid status"

  • Status must be active, expired, or cancelled
  • 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:

bash
php plugins/icota-members/scripts/backfill-user-chapters.php

Process:

  1. Finds all memberships with chapterId set
  2. Collects all chapters for each user
  3. Validates chapter UIDs are valid sites
  4. Always ensures ICoTA Global is assigned
  5. Updates user's chapter field 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:

bash
php plugins/icota-members/scripts/backfill-legacy-chapter.php

Process:

  1. Finds all legacy memberships without chapterId
  2. Reads each user's chapter field (Sites field)
  3. Selects the first non-Global chapter if available
  4. Falls back to Global chapter if that's the only assignment
  5. 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

bash
# 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.php

When 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

  1. Test with a small sample (10-20 records)
  2. Verify chapter mappings in chapters.json
  3. Check date formats in source CSV
  4. Ensure email addresses are valid

After Import

  1. Check import summary for errors and skipped rows
  2. Review a few user accounts in Craft CP:
    • Verify chapter assignments
    • Check custom field values
  3. Query membership records:
    bash
    craft _icota-members/memberships/stats
  4. Check for users without memberships or vice versa

Database Checks

You can verify imports directly in the database:

sql
-- 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

  1. Clean your source data first:

    • Remove duplicate email addresses
    • Standardize date formats
    • Verify chapter names match chapters.json
  2. Backup before importing:

    • Database backup
    • Keep original CSV files
    • Document any data transformations
  3. Start small:

    • Import 10-20 test records first
    • Verify results before full import
    • Use --skip-errors for bulk imports

Chapter Management

  1. Update chapters.json before import:

    • Ensure all chapter names are listed
    • Verify UUIDs match Craft site UIDs
    • Document any custom chapter mappings
  2. ICoTA Global is automatic:

    • Don't manually add it to source data
    • The script adds it automatically
    • Always validates it's present

Error Handling

  1. Review all warnings and errors:

    • Note which rows failed
    • Investigate root causes
    • Fix and re-import if needed
  2. Use --skip-errors wisely:

    • Good for bulk imports with known issues
    • Review skipped rows afterward
    • Don't use if you need 100% success rate
  3. Check logs:

    • Craft logs: storage/logs/web.log
    • Import summary output
    • Database for incomplete records

Troubleshooting

Common Issues

Chapter names not converting to UUIDs:

  • Check chapters.json has 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 source column value to import again

Dates not parsing correctly:

  • Check the date format in your CSV
  • Use 2025-12-31 format for best compatibility
  • Avoid ambiguous formats like 12/31/25

Chapter field not populating:

  • Ensure the chapter field exists on users
  • Verify it's a Sites field type
  • Check field mapping during user import

Getting Help

If you encounter issues:

  1. Review this guide thoroughly
  2. Check the main documentation (documentation.md)
  3. Review script output for specific error messages
  4. Check Craft logs for detailed errors
  5. Verify database state manually

Appendix

Chapter UUID Reference

From plugins/icota-members/chapters.json:

UUIDChapter Name
e3cae15b-9623-4eb8-95db-8f96f1a691a8USA Chapter
19cd36d2-fa3e-4a3f-9dd3-490fa27d5df0Asia Chapter
022de685-6eff-408b-b93b-803577ab7c43European Chapter
9e668174-c8fa-432e-beba-0a87d7f9e988Canadian Chapter
eae60906-eb70-43a5-a64a-7b1ab761ab13Latin America Chapter
aa3b0b2c-6d8e-4b99-bb90-852cbbe629f7Middle East & North Africa Chapter
bda224ee-134c-4b40-9f78-55b152f80927China Chapter
eef47251-977c-4035-bb07-9623c8b0b457ICoTA 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
  • Main plugin documentation: documentation.md
  • Developer context: plugins/icota-members/CLAUDE.md
  • Craft CMS user import: Craft CMS Documentation

ICoTA Members Plugin Documentation