Import & Export
Miranda supports three import pathways: CSV file upload, Stripe customer import, and direct JSON API ingest. All pathways handle deduplication, relationship assignment, tag application, and activity logging.
CSV import workflow
Section titled “CSV import workflow”The CSV import is a two-step process: upload (parse and preview) then confirm (execute the import).
Step 1: Upload and preview
Section titled “Step 1: Upload and preview”POST /api/import/uploadContent-Type: multipart/form-data
file: contacts.csvResponse:
{ "headers": ["first_name", "last_name", "email", "phone"], "preview": [ ], "file_path": "imports/abc123.csv", "total_rows": 150}The parser handles:
- BOM stripping — Removes UTF-8 BOM from files exported by Excel
- Delimiter detection — Auto-detects comma, semicolon, or tab delimiters
- Header normalization — Lowercases and trims all header names
Step 2: Map columns and confirm
Section titled “Step 2: Map columns and confirm”POST /api/import/confirmContent-Type: application/json
{ "file_path": "imports/abc123.csv", "column_mapping": { "first_name": "first_name", "last_name": "last_name", "email": "email", "phone": "phone" }, "options": { "relationship_type": "donor", "tags": ["imported-2026", "csv-upload"] }}Supported field mappings
Section titled “Supported field mappings”| Miranda Field | Description |
|---|---|
first_name | Contact first name |
last_name | Contact last name |
email | Email address (used for deduplication) |
phone | Phone number |
address_street | Street address |
address_city | City |
address_state | State |
address_zip | ZIP/postal code |
tags | Comma-separated tag names |
Import options
Section titled “Import options”| Option | Type | Default | Description |
|---|---|---|---|
relationship_type | string | general | ContactRelationshipType to assign (donor, applicant, subscriber, general) |
tags | string[] | [] | Global tags applied to all imported contacts |
Deduplication
Section titled “Deduplication”Email is the deduplication key. For each row:
- If a contact with the same email exists in the foundation, update their fields (name, phone, address)
- If no match exists, create a new contact
- Rows without a first name AND email are skipped
Import results
Section titled “Import results”{ "status": "completed", "counts": { "created": 120, "updated": 25, "skipped": 3, "errors": 2 }}Stripe customer import
Section titled “Stripe customer import”ImportService::importFromStripe() pulls customer and charge data from a connected Stripe account:
$counts = $importService->importFromStripe($foundationId, $stripeAccountId);For each Stripe customer with an email address:
- Parses the customer name into first/last name
- Retrieves all successful charges to calculate giving metrics
- Creates or updates the contact record
- Assigns the
donorrelationship with metadata:
{ "lifetime_giving": 1250.00, "gift_count": 5, "first_gift_date": "2025-06-15", "last_gift_date": "2026-03-01", "stripe_customer_id": "cus_abc123"}Customers without an email are skipped.
API JSON ingest
Section titled “API JSON ingest”For programmatic imports, the JSON ingest endpoint accepts an array of contact objects directly:
POST /api/import/ingestContent-Type: application/json
{ "contacts": [ { "first_name": "Jane", "last_name": "Doe", "email": "[email protected]", "phone": "555-1234", "tags": "donor,board-member" } ], "options": { "relationship_type": "donor", "tags": ["api-import"] }}Validation rules:
contacts— Required, array, minimum 1 itemcontacts.*.first_name— Required, string, max 255contacts.*.last_name— Nullable, string, max 255contacts.*.email— Nullable, valid email, max 255contacts.*.phone— Nullable, string, max 50contacts.*.tags— Nullable, comma-separated string
What happens during import
Section titled “What happens during import”For every contact imported (regardless of pathway):
- Contact created or updated (deduplicated by email)
- Relationship assigned via
addRelationship()(idempotent) - Tags applied (from CSV column values + global tags, auto-created if new)
- Activity logged with
ActivityType::Importedand source metadata
API endpoints
Section titled “API endpoints”| Method | Path | Description |
|---|---|---|
| POST | /api/import/upload | Upload CSV, get headers and preview |
| POST | /api/import/confirm | Execute import with column mapping |
| GET | /api/import/status/{importId} | Check import status (placeholder for future async) |
| POST | /api/import/ingest | Direct JSON import |