A simple, modern TypeScript library for creating VCard 3.0 (RFC 2426) formatted strings. Zero dependencies, fully typed, and works in both browser and Node.js environments.
- ✅ RFC 2426 Compliant - Strictly follows the VCard 3.0 specification
- 🎯 Simple API - No complex builder patterns, just plain objects
- 🔒 Fully Typed - Complete TypeScript type definitions
- 📦 Zero Dependencies - Lightweight and fast
- 🌐 Universal - Works in browser and Node.js
- ✨ Modern - ESM + CommonJS, tree-shakeable
- 🧪 Well Tested - Comprehensive test coverage
- 🍏 iOS URL Labels - Supports multiple URLs with human-friendly labels in Apple Contacts
While VCard 4.0 exists, as of right now 3.0 remains the most widely supported format.
npm install vcard-tsimport { formatVCard, type VCard } from 'vcard-ts';
const vcard: VCard = {
version: '3.0',
formattedName: 'John Doe',
name: {
familyName: 'Doe',
givenName: 'John',
},
};
const vcardString = formatVCard(vcard);
console.log(vcardString);Output:
BEGIN:VCARD
VERSION:3.0
FN:John Doe
N:Doe;John;;;
END:VCARD
Some legacy vCard consumers expect or require a CHARSET=... parameter on text properties.
Note: CHARSET is not defined in RFC 2426 itself; this library supports it as a legacy interoperability option.
By default, the formatter assumes UTF-8 and does not emit any CHARSET parameters.
import { formatVCard, type VCard } from 'vcard-ts';
const vcard: VCard = {
version: '3.0',
formattedName: 'José García',
name: {
familyName: 'García',
givenName: 'José',
},
};
const vcardString = formatVCard(vcard);
console.log(vcardString);Output:
BEGIN:VCARD
VERSION:3.0
FN:José García
N:García;José;;;
END:VCARD
If you need legacy interoperability, you can explicitly declare a charset via formatter options:
const vcardString = formatVCard(vcard, { charset: 'ISO-8859-1' });You can include media either as a URI reference or inline base64 data:
// URI Reference
const vcardWithPhoto: VCard = {
version: '3.0',
formattedName: 'Jane Smith',
name: { familyName: 'Smith', givenName: 'Jane' },
photo: {
uri: 'https://example.com/photo.jpg',
mediaType: 'JPEG',
},
};
// Inline Base64 (must be pre-encoded)
const vcardWithInlinePhoto: VCard = {
version: '3.0',
formattedName: 'Jane Smith',
name: { familyName: 'Smith', givenName: 'Jane' },
photo: {
value: 'base64EncodedDataHere...',
encoding: 'b',
mediaType: 'JPEG',
},
};Some Android/Google Contacts imports can mis-parse folded lines (RFC 2426 line folding with CRLF + space), especially for structured properties like ADR. If you see parts of the address appear as a name suffix or the address disappears, disable folding:
import { formatVCard, type VCard } from 'vcard-ts';
const vcard: VCard = {
version: '3.0',
formattedName: 'Jane Smith',
name: { familyName: 'Smith', givenName: 'Jane' },
addresses: [
{
street: '100 Tech Plaza',
locality: 'San Francisco',
region: 'CA',
postalCode: '94105',
country: 'United States of America',
types: ['work'],
},
],
};
// Keeps ADR on a single line (no folded continuations)
const vcardString = formatVCard(vcard, { foldLines: false });The library automatically escapes special characters according to RFC 2426:
const vcard: VCard = {
version: '3.0',
formattedName: 'Test; User, with\\special chars',
name: {
familyName: 'User',
givenName: 'Test',
},
note: 'Line 1\nLine 2',
};
// Special characters are automatically escaped:
// ; -> \;
// , -> \,
// \ -> \\
// newline -> \nMany consumers (including iOS Contacts) support multiple URL entries but won’t display a meaningful label unless you use Apple’s extension fields.
This library supports that via urls, emitting itemN.URL + itemN.X-ABLabel. The item1., item2., etc. prefix is the vCard group syntax: it scopes multiple related lines under the same group name. So that item1.X-ABLabel can be associated with item1.URL.
The X-ABLabel property is an Apple-specific X- extension used by iOS Contacts to display a human-friendly label for the preceding itemN.URL field (e.g. “LinkedIn”, “Website”). This is usually not supported on Android.
import { formatVCard, type VCard } from 'vcard-ts';
const vcard: VCard = {
version: '3.0',
formattedName: 'Michael Wolz',
name: { familyName: 'Wolz', givenName: 'Michael' },
urls: [
{ value: 'https://www.linkedin.com/in/michaelwolz', type: 'LinkedIn' },
{ value: 'https://michaelwolz.de', type: 'Website' },
],
};
console.log(formatVCard(vcard));Output (excerpt):
item1.URL:https://www.linkedin.com/in/michaelwolz
item1.X-ABLabel:LinkedIn
item2.URL:https://michaelwolz.de
item2.X-ABLabel:Website
Notes:
- iOS: shows both URLs and the labels.
- Android: typically shows both URLs, but often ignores the
X-ABLabellabels. - vCard 4.0 supports richer, standardized URL typing, but vCard 3.0 tends to have broader real-world compatibility; this is a practical workaround.
The main VCard type representing a complete vCard object.
Required Fields:
version: '3.0'- Must be "3.0"formattedName: string- Formatted name (FN property)name: Name- Structured name (N property)
Optional Fields:
See the TypeScript definitions for the complete list of optional fields including:
- Identification:
nickname,photo,birthday - Delivery Addressing:
addresses,labels - Telecommunications:
phones,emails,mailer - Geographical:
timezone,geo - Organizational:
title,role,logo,agent,organization - Explanatory:
categories,note,productId,revision,sortString,sound,url,uid - URLs:
url(single) andurls(multiple + optional iOS labels) - Security:
class,key
function formatVCard(vcard: VCard, options?: { charset?: 'UTF-8' | 'ISO-8859-1' | 'US-ASCII' }): string;
// You can also disable RFC line folding for Android/Google Contacts compatibility:
// formatVCard(vcard, { foldLines: false })Converts a VCard object into an RFC 2426 compliant VCard 3.0 string.
Parameters:
vcard: VCard- The VCard object to formatoptions?: { charset?: 'UTF-8' | 'ISO-8859-1' | 'US-ASCII'; fold?: boolean }- Optional formatting optionscharset- (Legacy) Charset to declare viaCHARSETparameters on text properties. If omitted, UTF-8 is assumed and noCHARSETis emitted.foldLines- Whether to apply RFC 2426 line folding (75 characters, CRLF + space). Default:true. Set tofalseif Android/Google Contacts mis-parses folded ADR lines.
Returns:
string- RFC 2426 compliant VCard string with CRLF line endings
Features:
- Automatic text escaping for special characters
- Line folding for lines longer than 75 characters
CHARSETparameters on text properties (legacy; only emitted when explicitly requested)- Proper formatting of all VCard 3.0 properties
- Date/DateTime formatting
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build the library
npm run build
# Type check
npm run typecheck
# Lint code
npm run lint
# Format code
npm run formatWorks in all modern browsers and Node.js 16+.
Contributions are welcome! Please ensure all tests pass and follow the existing code style.