Skip to content

A simple, zero-dependency TypeScript library for generating RFC 2426 (vCard 3.0) strings in Node.js and browsers.

License

Notifications You must be signed in to change notification settings

michaelwolz/vcard-ts

Repository files navigation

vcard-ts header image

vcard-ts

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.

Features

  • 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

Why VCard 3.0?

While VCard 4.0 exists, as of right now 3.0 remains the most widely supported format.

Installation

npm install vcard-ts

Usage

Basic Example

import { 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

Character Set Support

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' });

Photo/Logo/Sound

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',
  },
};

Android / Google Contacts Import Note

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 });

Special Characters

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 -> \n

Multiple URLs + iOS Labels

Many 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-ABLabel labels.
  • vCard 4.0 supports richer, standardized URL typing, but vCard 3.0 tends to have broader real-world compatibility; this is a practical workaround.

API Reference

Type: VCard

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) and urls (multiple + optional iOS labels)
  • Security: class, key

Function: formatVCard

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 format
  • options?: { charset?: 'UTF-8' | 'ISO-8859-1' | 'US-ASCII'; fold?: boolean } - Optional formatting options
    • charset - (Legacy) Charset to declare via CHARSET parameters on text properties. If omitted, UTF-8 is assumed and no CHARSET is emitted.
    • foldLines - Whether to apply RFC 2426 line folding (75 characters, CRLF + space). Default: true. Set to false if 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
  • CHARSET parameters on text properties (legacy; only emitted when explicitly requested)
  • Proper formatting of all VCard 3.0 properties
  • Date/DateTime formatting

Development

# 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 format

Browser Support

Works in all modern browsers and Node.js 16+.

Contributing

Contributions are welcome! Please ensure all tests pass and follow the existing code style.

References

About

A simple, zero-dependency TypeScript library for generating RFC 2426 (vCard 3.0) strings in Node.js and browsers.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •