astrotomic/laravel-vcard
Generate and download vCard (.vcf) files in Laravel with a fluent, expressive builder. Map Eloquent models or plain data to vCard 3.0/4.0 contact cards, customize fields, and serve them as downloadable files or responses—ideal for adding "Save contact" features to any app.
composer require astrotomic/laravel-vcard. The service provider and VCard facade are auto-discovered; there is nothing to register by hand in a standard Laravel app.php artisan vendor:publish --tag=vcard-config (or check src/ for a config/ directory). If nothing is published, the package is convention-driven and requires no configuration..vcf), then the facade class and the underlying builder in src/, then the test suite — the tests are the authoritative list of supported fields and method names, which matters for a 0.x package where the fluent API is still moving.return VCard::make()
->name($user->name)
->email($user->email)
->phone($user->phone)
->download("{$user->slug}.vcf");
0.x releases have renamed and reordered builder methods between minors.On 0.8.1 specifically: the release is a patch on top of 0.8.0 with no documented behavioural changes. If you are already on 0.8.x, treat it as a drop-in upgrade; if you are starting fresh, pin ^0.8 and start here.
VCardController (or an invokable controller with route-model binding) keeps the fluent chain out of routes/web.php and makes the endpoint testable.App\Support\VCardFactory::forUser($user) or a toVCard() presenter class — rather than repeating the chain in a controller and a queued job. This is where you branch on optional fields (filled($user->phone) ? ->phone(...) : $card) and where you centralise escaping rules.tap()-based helper so a missing address does not emit an empty ADR:;;;; block that some clients render as a blank entry.Storage::path()), and a raw string for tests or for embedding in a signed URL flow. Keep the filename in one constant/config value so Content-Disposition and the stored path never drift.BEGIN:VCARD ... END:VCARD blocks. For an "export my team" feature, generate each card and concatenate with the required CRLF separation rather than zipping N files — most address books import the combined file in one step.Storage::disk('public')->get($path)), and downsample before embedding. Base64 photos are the single biggest driver of file size and client-side import failures.Content-Type: text/vcard; charset=utf-8 and an explicit attachment filename yourself if you bypass the package's download() helper — mobile Safari and Outlook are picky about the MIME type and will show the raw text instead of offering an import.BEGIN:VCARD/END:VCARD presence and key fields rather than exact full-string equality where you can; when you do need a snapshot, normalise line endings first. Also assert the Content-Disposition header — a regression there breaks the "add to contacts" flow silently.\r\n line endings. If you post-process the output (trimming, templating, storing in a DB column), verify you have not collapsed them; some parsers reject LF-only files.NOTE, ADR, and photo payloads are folded onto continuation lines. This is correct behaviour, but it breaks naive string assertions and any regex you write against the raw output — test field-by-field or unfold before asserting.N, ADR, ORG) use ; and , as delimiters. A company name like Acme, Inc. or an address line containing a semicolon must be escaped, or the card will import with fields shifted. This is the most common source of "it looked fine in the dump but Contacts shows garbage" bugs.^0.8 (or exact) and read the compare link in the release notes before taking 0.9. The 0.8.1 patch carries no documented changes, so no migration work is expected from 0.8.0.Storage::put() returning true says nothing about public accessibility. If the .vcf is meant to be fetched by a client, put it on a disk with the correct visibility or serve it through a controller instead.dd($card->toString()) (or whatever the string accessor is in this version) before wiring up the HTTP response. Then validate by actually importing the file into macOS Contacts, Google Contacts, and iOS — a card that opens correctly in a text editor still fails import surprisingly often.How can I help you explore Laravel packages today?