Imported from monicahq/monica#7919
Originally filed by @cabraham2 on 2026-01-08
Status when imported: open
Summary
This PR implements a complete vCard import feature with a web UI, enabling users to import contacts from standard vCard files (.vcf) with full support for organizations, photos, notes, and all standard contact fields.
Motivation
Monica currently lacks a user-facing way to import contacts from vCard files (the standard format used by Google Contacts, iCloud, Outlook, etc.). Users migrating from other contact management systems cannot easily bring their data into Monica.
What This Adds
New Web UI
- Import page at
/vaults/{vault}/contacts/import
- Upload vCard files and preview all contacts before import
- Select which contacts to import (checkbox selection)
- Progress bar with real-time percentage during import
- Error details showing which contacts failed and why
Complete vCard Support
- Organizations (ORG field): Links contacts to companies via
company_id
- Avatars (PHOTO field): Imports contact photos
- Job Information (TITLE field): Preserves job titles
- Notes (NOTE field): Imports all notes
- Addresses: Full support with proper formatting (HOME/WORK/OTHER)
- Contact Information: Phone, email, all types supported
Technical Implementation
- Chunked Processing: Handles large files (1000+ contacts) without PHP timeout
- Processes 50 contacts per batch
- Real-time progress tracking
- Robust Error Handling:
- Detailed error messages for failed imports
- Shows contact names and reasons for failure
- Continues processing remaining contacts on error
- Clean Architecture:
- New DAV importers:
ImportJobInformation, ImportAvatar, ImportNotes
- Order attribute system for proper import sequence
- Complete PHPDoc documentation
Real-World Testing
Tested with Google Contacts export (1,260 contacts, 2.7 MB):
- ✅ 1,238 contacts imported successfully (98.3% success rate)
- ❌ 22 failures = invalid vCards in source file (no names/empty entries)
- ⚡ No timeouts with chunked processing
- 🔄 Full progress feedback to users
Technical Documentation
See docs/VCARD_IMPORT_ENHANCEMENTS.md for:
- Complete architecture overview
- Import flow sequence diagrams
- Order attribute system explanation
- Error handling strategy
Files Added
Controllers & Services:
ContactImportController.php - Web endpoint for import
ParseVCardFile.php - vCard parsing service
ContactImportViewHelper.php - Data preparation for UI
DAV Importers:
ImportJobInformation.php - Organization/job import
ImportAvatar.php - Photo import
ImportNotes.php - Notes import
Frontend:
resources/js/Pages/Vault/Contact/Import/Index.vue - Import UI
Tests:
- Unit tests for
ParseVCardFile
- Feature tests for
ContactImportController
Breaking Changes
None. This is a new feature that doesn't affect existing functionality.
Checklist
🏗️ Architecture - Diagramme de flux
┌─────────────────────────────────────────────────────────────────┐
│ Import vCard (.vcf file) │
│ ContactImportController │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Sabre\VObject\Reader::read($vcard) │
│ Parse vCard format (RFC 6350) │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DAV Import Pipeline │
│ Multiple Importers (Order attribute) │
└────────────────────────────┬────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌─────────────┐ ┌────────────────────┐
│ ImportAvatar │ │ ImportNotes │ │ ImportJobInform.. │
│ Order(20) │ │ Order(30) │ │ Order(41) - NEW │
│ │ │ │ │ │
│ PHOTO field → │ │ NOTE field │ │ ORG field → │
│ storage/photos │ │ → notes tbl │ │ Company + Link │
└──────────────────┘ └─────────────┘ └────────────────────┘
│ │ │
│ ▼ ▼
│ ┌──────────────┐ ┌──────────────────┐
│ │ CreateNote() │ │ CreateCompany() │
│ └──────────────┘ │ or find existing │
│ │ │
│ │ UpdateJobInfo() │
│ │ → contacts. │
│ │ company_id │
│ └──────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ImportAddress │
│ Order(40) │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌───────────────────┴───────────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐
│ getAddressType() │ │ formatStreetAddress() │
│ │ │ - NEW FUNCTION │
│ HOME,pref → home │ │ │
│ Create if missing │ │ "61\nRue" → "61, Rue" │
│ │ │ "61Rue" → "61, Rue" │
└─────────────────────┘ └─────────────────────────┘
│ │
└───────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ vCard ADR field mapping │
│ │
│ parts[0]: PO Box (unused) │
│ parts[1]: Extended (apartment/suite) → line_2 │
│ parts[2]: Street → line_1 (via formatStreetAddress) │
│ parts[3]: City │
│ parts[4]: Province │
│ parts[5]: Postal Code │
│ parts[6]: Country │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ UpdateAddress() or CreateAddress() │
│ + AssociateAddressToContact() │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Contact Feed Display │
│ ActionFeedAddress helper │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌───────────────────┴───────────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐
│ Map Image (opt.) │ │ Address Display │
│ │ │ │
│ MapHelper:: │ │ line_1: "61, Rue..." │
│ getStaticImage() │ │ line_2: "Apt 2" │
│ │ │ city, province, etc. │
│ If Mapbox config: │ │ │
│ → Generate URL │ │ Type: 🏡 Home │
│ │ │ │
│ If not configured: │ │ View on map (OSM link) │
│ → Return null │ │ │
│ → No broken image │ └─────────────────────────┘
└─────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ContactModuleAddressImageController::show() │
│ │
│ If MapHelper returns null → abort(404) │
│ Else → Proxy Mapbox API request → Stream image │
└─────────────────────────────────────────────────────────────────┘
Made with ❤️ for Monica CRM
Summary
This PR implements a complete vCard import feature with a web UI, enabling users to import contacts from standard vCard files (.vcf) with full support for organizations, photos, notes, and all standard contact fields.
Motivation
Monica currently lacks a user-facing way to import contacts from vCard files (the standard format used by Google Contacts, iCloud, Outlook, etc.). Users migrating from other contact management systems cannot easily bring their data into Monica.
What This Adds
New Web UI
/vaults/{vault}/contacts/importComplete vCard Support
company_idTechnical Implementation
ImportJobInformation,ImportAvatar,ImportNotesReal-World Testing
Tested with Google Contacts export (1,260 contacts, 2.7 MB):
Technical Documentation
See
docs/VCARD_IMPORT_ENHANCEMENTS.mdfor:Files Added
Controllers & Services:
ContactImportController.php- Web endpoint for importParseVCardFile.php- vCard parsing serviceContactImportViewHelper.php- Data preparation for UIDAV Importers:
ImportJobInformation.php- Organization/job importImportAvatar.php- Photo importImportNotes.php- Notes importFrontend:
resources/js/Pages/Vault/Contact/Import/Index.vue- Import UITests:
ParseVCardFileContactImportControllerBreaking Changes
None. This is a new feature that doesn't affect existing functionality.
Checklist
🏗️ Architecture - Diagramme de flux
Made with ❤️ for Monica CRM