Why Generate vCard Files Dynamically in CodeIgniter 4?
In today’s digital world, sharing contact information quickly and reliably is essential for businesses, freelancers, event organizers, CRM systems, and directory websites. Instead of manually creating .vcf files or asking users to fill out forms every time, dynamically generating vCard files directly from your database offers a seamless, professional experience.With CodeIgniter 4, you can build a powerful contact management system where users (or admins) add contacts once, and visitors can instantly download properly formatted vCard (.vcf) files with a single click. This eliminates typos, saves time, improves mobile compatibility (most phones import vCards natively), and boosts user satisfaction. Search engines also favor pages that solve real user problems — tutorials showing “how to generate vCard in CodeIgniter 4” or “dynamic vCard download PHP” consistently attract organic traffic from developers and small business owners looking for ready-to-use solutions.
Whether you're building a company directory, professional networking tool, lead capture system, or employee contact page, implementing dynamic vCard generation in CodeIgniter 4 adds real value, increases engagement, and makes your application stand out. In this step-by-step guide, you’ll learn exactly how to create, format, and force-download vCard files from database records — complete with clean code, proper headers, character escaping, and best practices for production use.
Key benefits at a glance:
- One-click contact export to phones, Outlook, Google Contacts
- Reduces manual data entry errors
- Improves UX on mobile and desktop
- SEO-friendly feature that keeps visitors on your site longer
- Easy to extend with QR codes, photos, addresses, social links
Ready to add this powerful feature to your CodeIgniter 4 project? Let’s dive into the complete implementation.

Table Of Content
1 Prerequisites
2.) Composer
3.) Mysql
2 Introduction
We'll build a simple contact list with DataTables, add new contacts via popup, and provide a "Download vCard" button per record.
3 Create / Install a Codeigniter 4 Project
3.1 Install Codeigniter 4 Project
Use the following command to install new Codeigniter Project.
composer create-project codeigniter4/appstarter ci-4-vcard-app
Then, navigate to your project directory:
cd ci-4-vcard-app
3.2 Configure Environment and MySql Database
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ci4_vcard
DB_USERNAME=root
DB_PASSWORD=
Create the database ci4_vcard
4 Create Migration and Model
php spark make:model Contact
Edit app/Models/Contact.php to configure the model:
<?php
namespace App\Models;
use CodeIgniter\Model;
class Contact extends Model
{
protected $table = 'contacts';
protected $primaryKey = 'id';
protected $allowedFields = ['name','email','phone','created_at','updated_at'];
}
Create a migration file for the contacts table:
php spark make:migration CreateContactsTable
Edit the migration file to define the table structure:
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateContactsTable extends Migration
{
public function up()
{
$this->forge->addField([
'id' => [
'type' => 'BIGINT',
'constraint' => 255,
'unsigned' => true,
'auto_increment' => true
],
'name' => [
'type' => 'VARCHAR',
'constraint' => '255',
],
'email' => [
'type' => 'longtext'
],
'phone' => [
'type' => 'longtext'
],
'created_at' => [
'type' => 'TIMESTAMP',
'null' => true
],
'updated_at' => [
'type' => 'TIMESTAMP',
'null' => true
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->createTable('contacts');
}
public function down()
{
$this->forge->dropTable('contacts');
}
}
Run the migration:
php spark migrate
5 Create ContactController
php spark make:controller ContactController
Controller Function to Export and Download vCard in CodeIgniter:
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use CodeIgniter\HTTP\ResponseInterface;
use App\Models\Contact;
class ContactController extends BaseController
{
public function index()
{
return view('contacts');
}
public function store()
{
$jsonStr = file_get_contents('php://input');
$jsonObj = json_decode($jsonStr);
$contact_data = $jsonObj->contact_data;
$model = new Contact();
$data = [
"name" => $contact_data[0],
"email" => $contact_data[1],
"phone" => $contact_data[2],
"created_at"=>date('Y-m-d H:i:s')
];
$model->insert($data);
$json_data['status']=1;
return $this->response->setJSON($json_data);
}
public function getUsers()
{
$model = new Contact();
$request = \Config\Services::request();
$limit = $request->getPost('length');
$start = $request->getPost('start');
$order_column_value = $request->getPost('order')[0]['column'];
$order=$request->getPost('columns')[$order_column_value]['data'];
$dir = $request->getPost('order')[0]['dir'];
$totalData = $model->countAll();
$totalFiltered = $totalData;
if(empty($request->getPost('search')['value']))
{
$users = $model->orderBy($order, $dir)->findAll($limit, $start);
}
else {
$search = $request->getPost('search')['value'];
$users = $model->like('name', $search)->orLike('email', $search)->orderBy($order, $dir)->findAll($limit, $start);
$totalFiltered = $model->like('name', $search)->orLike('email', $search)->countAllResults();
}
$data = array();
if(!empty($users))
{
foreach ($users as $user)
{
$nestedData['id'] = $user['id'];
$nestedData['name'] = $user['name'];
$nestedData['email'] = $user['email'];
$nestedData['phone'] = $user['phone'];
$nestedData['created_at'] = $user['created_at'];
$nestedData['action'] = '<a class="btn btn-success" href="<?= base_url('contact/exportvcard/' . $user['id']) ?>" target="_blank">Download VCard</a>';
$data[] = $nestedData;
}
}
$json_data = array(
"draw" => intval($request->getPost('draw')),
"recordsTotal" => intval($totalData),
"recordsFiltered" => intval($totalFiltered),
"data" => $data
);
return $this->response->setJSON($json_data);
}
public function exportvcard($id = null)
{
$model = new Contact();
$contact = $model->find($id);
if (!$contact) {
return $this->response->setStatusCode(404)->setBody('Contact not found');
}
// Build vCard string (v3.0 standard, with proper escaping)
$vcf = "BEGIN:VCARD\r\n";
$vcf .= "VERSION:3.0\r\n";
$vcf .= "FN:" . $this->escapeVcard($contact['name']) . "\r\n";
$vcf .= "N:" . $this->escapeVcard($contact['name']) . ";;;\r\n"; // Basic N field
$vcf .= "EMAIL;TYPE=WORK:" . $this->escapeVcard($contact['email']) . "\r\n";
$vcf .= "TEL;TYPE=CELL,VOICE:" . $this->escapeVcard($contact['phone']) . "\r\n";
$vcf .= "REV:" . date('Ymd\THis\Z') . "\r\n";
$vcf .= "END:VCARD\r\n";
$filename = str_replace(' ', '_', $contact['name']) . '.vcf';
return $this->response
->setHeader('Content-Type', 'text/vcard; charset=utf-8')
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
->setBody($vcf);
}
private function escapeVcard($string)
{
// Basic escaping for vCard (add more for commas, semicolons, etc. as needed)
return str_replace(["\r", "\n", ",", ";"], ['\\r', '\\n', '\\,', '\\;'], $string);
}
}
?>
This demonstrates how to generate vCard files in CodeIgniter and facilitates downloading them. 6 Create View (contacts.php)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> CodeIgniter 4 vCard Generator</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.1/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.datatables.net/1.11.4/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.22/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
* {
margin: 0;
padding: 0;
font-family: 'Poppins', sans-serif;
}
body {
background-color: #eee !important;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
}
.main {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 40px 30px 40px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 15px;
box-shadow: rgba(0, 0, 0, 0.3) 0 5px 15px;
width: 60%;
height: 700px;
position: absolute;
}
.header-container {
display: flex;
justify-content: space-between;
width: 100%;
border-bottom: 1px solid;
margin-bottom: 20px;
padding-bottom: 10px;
}
.header-container > h3 {
font-weight: 500;
}
.tasks-container {
position: relative;
width: 100%;
}
.action-button {
display: flex;
justify-content: center;
}
.action-button > button {
width: 25px;
height: 25px;
font-size: 17px;
display: flex !important;
justify-content: center;
align-items: center;
margin: 0px 2px;
}
.dataTables_wrapper .dataTables_info {
position: absolute !important;
bottom: 20px !important;
}
</style>
</head>
<body>
<div class="main">
<div class="container">
<div class="header-container">
<h3>Contact Manager with Export to VCF</h3>
<button type="button" class="btn btn-dark btn-sm" onClick="addContact()">
Add Contact
</button>
</div>
<div class="tasks-container">
<table id="userTable" class="table table-bordered ">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<!-- Data will be loaded here -->
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function() {
loadData();
});
function loadData()
{
$('#userTable').DataTable({
"processing": true,
"serverSide": true,
"ajax": {
"url": "<?php echo base_url('contact/getUsers''); ?>",
"type": "POST"
},
"columns": [
{ data: 'id' },
{ data: 'name' },
{ data: 'email' },
{ data: 'phone' },
{ data: 'created_at' },
{ data: 'action' }
]
});
}
function addContact()
{
(async () => {
const { value: formValues } = await Swal.fire({
title: 'Add Contact',
confirmButtonText: 'Submit',
showCloseButton: true,
showCancelButton: true,
html:
'<input id="name" class="swal2-input" placeholder="Enter Name">' +
'<input id="email" class="swal2-input" placeholder="Enter Email Id">' +
'<input id="phone" class="swal2-input" placeholder="Enter Phone No">',
focusConfirm: false,
preConfirm: () => {
return [
document.getElementById('name').value,
document.getElementById('email').value,
document.getElementById('phone').value
]
}
});
if (formValues) {
// Add event
fetch("/store", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contact_data: formValues}),
})
.then(response => response.json())
.then(data => {
if (data.status == 1) {
Swal.fire('Contact added successfully!', '', 'success');
loadData();
} else {
Swal.fire(data.error, '', 'error');
}
// Refetch events from all sources and rerender
calendar.refetchEvents();
})
.catch(console.error);
}
})()
}
</script>
</body>
</html>
7 Define a Route
use CodeIgniter\Router\RouteCollection;
/**
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');
$routes->get('contacts', 'ContactController::index');
$routes->post('contact/store', 'ContactController::store');
$routes->post('contact/getUsers', 'ContactController::getUsers');
$routes->get('contact/exportvcard/(:num)', 'ContactController::exportvcard/$1');
8 Folder Structure
9 Run and Test
php spark serve
Visit http://localhost:8080/contacts — add contacts, then download vCards. 10 Conclusion
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
A vCard (.vcf file) is a standard format for electronic business cards that allows users to easily save contact details like name, phone, email, and address to their devices. In web apps, dynamically generating vCards enables instant downloads without sto
In CodeIgniter 4, create a controller method that builds the vCard string using proper headers (Content-Type: text/vcard, Content-Disposition: attachment). Concatenate fields like BEGIN:VCARD, VERSION:3.0 or 4.0, N;, FN;, TEL;, EMAIL;, and END:VCARD, then
vCard 3.0 is widely supported across devices. vCard 4.0 adds more features but may have compatibility issues on older systems. For most applications, start with 3.0.
No, you can build it manually with string concatenation. However, for complex vCards (e.g., photos, multiple phones), consider Composer libraries like jeroendesloovere/vcard.
Set response headers in the controller: Content-Type: text/vcard; charset=utf-8 and Content-Disposition: attachment; filename=\"contact.vcf\". This triggers a download prompt.
Yes, in vCard 3.0 use PHOTO;ENCODING=BASE64;TYPE=JPEG: followed by base64-encoded image data. In 4.0, use PHOTO;VALUE=URI: for a URL or embedded base64.
Yes, properly formatted vCards (especially version 3.0) work well on both iOS and Android, allowing users to add contacts directly.
