Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Vcard Bundle Laravel Package

jeroendesloovere/vcard-bundle

Symfony bundle for generating and downloading vCard (VCF) contact files from PHP objects, wrapping the jeroendesloovere/vcard library with service integration and helpers for Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the verdict: this is a Symfony2 bundle, not a Laravel package, and it has not been touched since November 2018 (16 stars, zero dependents). Do not composer require jeroendesloovere/vcard-bundle in a Laravel app — its symfony/framework-bundle constraint is anchored to the Symfony 2.x line, which will collide with the Symfony 6/7 components that Laravel 10+ pulls in. You will get an unresolvable dependency graph before you get any value.

The bundle is a thin DI wrapper around the framework-agnostic jeroendesloovere/vcard library. That library is the thing you actually want. Install it directly:

composer require jeroendesloovere/vcard

Where to look first: src/VCard.php in the installed library plus the src/Properties/ directory. That file is the entire public surface — a builder that accumulates properties and emits a .vcf string via getOutput(). The bundle adds nothing you can't do with new VCard().

Your first use case — a "Save to contacts" endpoint. Almost every real-world use is some variant of "turn a database row into a downloadable .vcf":

// routes/web.php
Route::get('/contacts/{contact}/vcard', [ContactController::class, 'vcard']);
use JeroenDesloovere\VCard\VCard;

public function vcard(Contact $contact)
{
    $vcard = new VCard();
    $vcard->addName($contact->last_name, $contact->first_name);
    $vcard->addCompany($contact->company);
    $vcard->addEmail($contact->email, 'WORK');
    $vcard->addPhoneNumber($contact->phone, 'WORK');
    $vcard->addUrl($contact->website);

    $raw = $vcard->getOutput();

    return response($raw, 200, [
        'Content-Type'        => 'text/vcard; charset=utf-8',
        'Content-Disposition' => 'attachment; filename="'.Str::slug($contact->full_name).'.vcf"',
        'Content-Length'      => strlen($raw),
    ]);
}

That is the 90% case. Everything below is elaboration on it.

Implementation Patterns

1. Wrap the builder in a factory/service. Do not scatter new VCard() through controllers. One mapper class keeps the property list in one place and makes the output testable:

namespace App\Services;

use App\Models\Contact;
use JeroenDesloovere\VCard\VCard;

class VCardFactory
{
    public function forContact(Contact $contact): VCard
    {
        $vcard = new VCard();
        $vcard->addName($contact->last_name, $contact->first_name);
        $vcard->addCompany($contact->company);
        $vcard->addJobtitle($contact->job_title);
        $vcard->addEmail($contact->email, 'WORK');
        $vcard->addPhoneNumber($contact->phone, 'WORK');
        $vcard->addAddress(null, null, $contact->street, $contact->city,
                            $contact->region, $contact->postal_code, $contact->country);
        $vcard->addNote($contact->bio);

        return $vcard;
    }
}

Resolve it through the container (bind it in a service provider or just let Laravel autowire it into the controller) so the mapping is swappable per tenant/context.

2. Always build the HTTP response yourself. Use getOutput(), never the library's download() helper. Older versions of the library call PHP's header() directly and/or exit, which bypasses Laravel's response pipeline, breaks Content-Disposition filenames, and makes the route untestable. Returning response($raw, ...) gives you proper test assertions, middleware, and ETag/caching support.

3. Attach the vCard to outbound mail. Very common for sales/BD workflows — a rep's contact card in every signature:

// inside a Mailable's build() or attachments()
$this->attachData(
    $this->vcardFactory->forUser($this->rep)->getOutput(),
    'rep.vcf',
    ['mime' => 'text/vcard']
);

4. Bulk export. For "download the whole team's contacts", generate once per record and zip via ZipArchive into storage/app/tmp, then response()->download(...)->deleteFileAfterSend(true). Do the generation in a queued job if the set is large — base64 photos make each file non-trivial.

5. QR codes. getOutput() returns exactly the payload a QR encoder needs. Feed it into endroid/qr-code or a client-side library and you have a scannable "add me" card with no extra plumbing. Note that QR payload size limits (roughly 1–2 KB of practical density) mean photos are off the table here.

6. Custom/vendor properties. For anything the typed helpers don't cover — X-SOCIALPROFILE, X-ABLabel, pronouns — drop down to the generic property API (addProperty() / setProperty() depending on version). Check the README of the version you install; the v3 and v4 APIs differ.

7. Test the raw string, not the object. Snapshot assertions on the emitted text are the only reliable way to catch encoding and folding regressions:

$raw = app(VCardFactory::class)->forContact($contact)->getOutput();

$this->assertStringStartsWith("BEGIN:VCARD\r\n", $raw);
$this->assertStringContainsString("FN:John Doe", $raw);
$this->assertStringContainsString("EMAIL;TYPE=WORK:[email protected]", $raw);

Gotchas and Tips

  • API drift between library versions is real. The bundle pinned a 2.x/3.x-era dependency; the current standalone library is 4.x, where several methods were renamed (setFirstName/setLastName, setFormattedName, setPhoto, etc.). Copying snippets from the bundle's README into a 4.x install will produce "call to undefined method" errors. Read the README for the exact version in your composer.lock and pin it explicitly ("jeroendesloovere/vcard": "^4.0") so a minor bump can't silently rename things under you.
  • PHP 8.x compatibility. A 2018-era library will likely emit deprecation notices on PHP 8.2+ (implicitly nullable parameters, dynamic properties). Set error_reporting appropriately in dev, and if you're stuck on an old release, prefer upgrading the standalone library over pinning your PHP version.
  • Content-Type matters for client behavior. text/vcard; charset=utf-8 is the modern choice; text/x-vcard is the legacy one Outlook sometimes expects. Serve text/vcard and test on iOS, Android, and Outlook — they disagree about almost everything else.
  • ASCII filenames only. Non-ASCII characters in Content-Disposition get mangled by some clients; Str::ascii(Str::slug($name)) before appending .vcf.
  • Photos are the main size hazard. Base64-embedded images inflate the payload ~33% and many clients silently truncate at a few hundred KB. Resize to 200×200 JPEG with Intervention Image before embedding, and cap the source file size before you even read it.
  • Type tokens are version-sensitive. 'WORK', 'HOME', 'CELL' are the vCard 3.0 tokens; vCard 4.0 uses different casing/semantics. Passing a junk string produces a card that parses but drops the field in strict clients. Stick to the documented set.
  • Escaping is handled — but verify. Commas, semicolons, and newlines in NOTE, ORG, and ADR must be backslash-escaped. The library does this, but if you ever post-process the output string (templating, string replacement), you can un-escape it and corrupt the card. Never mutate getOutput().
  • Line folding. vCard 3.0 folds at 75 octets with a leading space on continuations. Don't "prettify" the output or add your own \r\n — you'll break the fold. If you must inspect, dump to a file and open it in a real client.
  • Cache the output. Generation is cheap for text-only cards, but photo-bearing cards aren't. Key a cache entry on $contact->updated_at->timestamp and store the raw .vcf string; invalidate naturally on save.
  • Extension point that actually matters: the generic addProperty()/setProperty() API. Everything the typed helpers can't express (custom X- fields, KIND:group, BDAY with partial dates) lives there. It's the escape hatch that keeps you from forking the library.
  • Don't expect maintenance. Zero dependents and no commits since 2018 means you own any bug you hit. Budget for vendoring a patch or wrapping the library behind your own VCardFactory interface so swapping to brick/vcard or hand-rolled output later is a one-file change.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console