What is a vCard and Why Generate vCard Files in Laravel 11?
A vCard (also known as a .vcf file) is a standard electronic business card format used to share contact information seamlessly across devices, apps, and platforms. It stores details like name, phone number, email, company, job title, address, website, photo, and even social links in a lightweight, universally compatible text file. When someone opens or imports your vCard, their phone or email client automatically adds the contact—no manual typing required.In today's digital-first world, generating dynamic vCards has become essential for professionals, freelancers, sales teams, and businesses. Instead of static paper cards, a Laravel-powered vCard lets you create personalized, updatable digital business cards pulled straight from your database. This is especially useful for CRM systems, employee directories, event apps, or SaaS platforms offering custom contact sharing.
With Laravel 11, you can build a robust vCard generator using modern packages like astrotomic/laravel-vcard or jeroendesloovere/vcard. These tools handle proper formatting, line endings, character encoding, and advanced fields (QR codes, logos, etc.), avoiding common pitfalls of manual string concatenation. Whether you're exporting single contacts or enabling bulk downloads, integrating vCard functionality improves user experience, boosts networking efficiency, and adds real value to your Laravel application in 2025–2026.
In this step-by-step tutorial, you'll learn exactly how to generate and download vCard (.vcf) files in Laravel 11 using best practices, a reliable package, and clean code. Let's get started!

Table Of Content
1 Prerequisites
- PHP 8.2+
- Composer
- Mysql
- Basic knowledge of Laravel, Eloquent, Blade, and routes
2 Introduction
3 Create / Install a Laravel Project
3.1 Install Laravel Project
composer create-project laravel/laravel laravel11-vcard-app
Navigate to your project directory:
cd laravel11-vcard-app
3.2 Configure Database (.env)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_vcard
DB_USERNAME=root
DB_PASSWORD=
4 Create Migration
php artisan make:migration create_contacts_table
In the migration file located at database/migrations, add the following code to define the contacts table structure:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('phone');
$table->string('company')->nullable();
$table->string('job_title')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('contacts');
}
};
Run the migration:
php artisan migrate
5 Create Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
use HasFactory;
protected $fillable = ['name', 'email', 'phone', 'company', 'job_title'];
}
6 Install vCard Package
composer require astrotomic/laravel-vcard
(Alternative: jeroendesloovere/vcard for simpler needs.) 7 Create Contact Controller
php artisan make:controller ContactController
Add the following methods to manage contacts and generate a Laravel vCard:
<?php
namespace App\Http\Controllers;
use App\Models\Contact;
use Astrotomic\Vcard\Vcard;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ContactController extends Controller
{
public function index()
{
$contacts = Contact::all();
return view('contacts.index', compact('contacts'));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:contacts',
'phone' => 'required|string|max:20',
'company' => 'nullable|string|max:255',
'job_title' => 'nullable|string|max:255',
]);
Contact::create($data);
return response()->json(['success' => true, 'message' => 'Contact added successfully']);
}
public function export($id)
{
$contact = Contact::findOrFail($id);
$vcard = Vcard::make()
->fullName($contact->name)
->email($contact->email)
->tel($contact->phone, ['pref', 'cell'])
->company($contact->company)
->jobtitle($contact->job_title)
->build();
$filename = Str::slug($contact->name) . '.vcf';
return response($vcard)
->header('Content-Type', 'text/vcard; charset=utf-8')
->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
}
}
?>
8 Create view (Blade)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laravel 11 vCard Generator</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- DataTables CSS -->
<link href="https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<!-- SweetAlert2 CSS (for notifications) -->
<link href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">Contact Management with vCard Export</h1>
<!-- Add Contact Button -->
<button class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#addContactModal">Add Contact</button>
<!-- Contacts Table -->
<table id="contactsTable" class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Company</th>
<th>Job Title</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach ($contacts as $contact)
<tr>
<td>{{ $contact->id }}</td>
<td>{{ $contact->name }}</td>
<td>{{ $contact->email }}</td>
<td>{{ $contact->phone }}</td>
<td>{{ $contact->company ?? 'N/A' }}</td>
<td>{{ $contact->job_title ?? 'N/A' }}</td>
<td>
<a href="{{ route('contacts.export', $contact->id) }}" class="btn btn-sm btn-success">Download vCard</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<!-- Add Contact Modal -->
<div class="modal fade" id="addContactModal" tabindex="-1" aria-labelledby="addContactModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addContactModalLabel">Add New Contact</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addContactForm">
@csrf
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="phone" class="form-label">Phone</label>
<input type="text" class="form-control" id="phone" name="phone" required>
</div>
<div class="mb-3">
<label for="company" class="form-label">Company (Optional)</label>
<input type="text" class="form-control" id="company" name="company">
</div>
<div class="mb-3">
<label for="job_title" class="form-label">Job Title (Optional)</label>
<input type="text" class="form-control" id="job_title" name="job_title">
</div>
<button type="submit" class="btn btn-primary">Save Contact</button>
</form>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
$('#contactsTable').DataTable({
"paging": true,
"searching": true,
"ordering": true,
});
// Handle Form Submission with AJAX
$('#addContactForm').on('submit', function(e) {
e.preventDefault();
$.ajax({
url: '{{ route("contacts.store") }}',
type: 'POST',
data: $(this).serialize(),
success: function(response) {
Swal.fire({
icon: 'success',
title: 'Success',
text: response.message,
}).then(() => {
location.reload(); // Reload to update table
});
},
error: function(xhr) {
Swal.fire({
icon: 'error',
title: 'Error',
text: xhr.responseJSON.message || 'An error occurred.',
});
}
});
});
});
</script>
</body>
</html>
9 Define Routes
use App\Http\Controllers\ContactController;
use Illuminate\Support\Facades\Route;
Route::get('/contacts', [ContactController::class, 'index'])->name('contacts.index');
Route::post('/contacts', [ContactController::class, 'store'])->name('contacts.store');
Route::get('/contacts/{id}/export', [ContactController::class, 'export'])->name('contacts.export');
10 Folder Structure
11 Run & Test
php artisan serve
Visit /contacts, add a contact, and download its vCard.
Common Issues & Troubleshooting
- vCard corrupted? Ensure \r\n line endings (package handles this).
- No download? Check headers and no extra output (e.g., whitespace).
- ]Add photo/QR? Extend with package methods like photo().
12 Conclusion
For advanced features, explore jeroendesloovere/vcard or build a full SaaS vCard platform.
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
You need PHP version 8.2 or higher, Composer, and a MySQL database configured in your .env file.
Run `composer create-project laravel/laravel your-project-name` and then configure your database in the .env file.
Manually build the vCard string with BEGIN:VCARD, VERSION:3.0, FN (full name), TEL, EMAIL, and END:VCARD. Set headers for Content-Type: text/vcard and Content-Disposition: attachment, then echo the content and exit.
In the Blade view, use a link or button pointing to the route like /exportvcard/{id}, which triggers the controller method to generate and download the .vcf file.
Yes, extend the vCard string with additional lines like ORG:CompanyName, ADR:;;Street;City;;Zip;Country, or PHOTO for base64-encoded images, following vCard 3.0 standards.
Common issues include missing or incorrect headers, extra output before echoing the vCard content, line ending mismatches (\n vs \r\n), or special characters in names/emails. Use \r\n for line endings and ensure no whitespace or echoes before headers.
Yes, consider using packages like jeroendesloovere/vcard or astrotomic/laravel-vcard for easier, more robust handling of vCard standards, multiple fields, and downloads.
A modal form uses SweetAlert2 and jQuery AJAX to POST data to the /store route, which saves the contact and refreshes the DataTables view.
