cgdprojects/laravel-gedcom
A Laravel package for working with GEDCOM genealogy files. Parse, read, and manipulate family tree data with an Eloquent-style API, making it easier to import GEDCOM records and build genealogy features in Laravel applications.
The package is a thin Laravel wrapper around a PHP GEDCOM parser, so the fastest path is to install it, load a sample file, and confirm you can walk the record tree before designing any storage schema.
Install and inspect.
composer require cgdprojects/laravel-gedcom
Then read in this order: composer.json (to see the underlying parser it wraps and the PHP/Laravel constraints), src/ for the service provider and any facade, and config/ if a config file is publishable. With only 22 stars, the source is the documentation — verify the actual API surface rather than trusting snippets in the README.
Publish config (if present) and register the facade or inject the service.
Laravel auto-discovery should register the provider, but check config/app.php if you have it disabled. If there is a config file, it typically governs the parser driver, default encoding behavior, or filesystem disk — publish it so you can tune it later.
Parse a trivial file first. GEDCOM is line-oriented, level-prefixed text:
0 @I1@ INDI
1 NAME John /Doe/
1 SEX M
1 BIRT
2 DATE 1 JAN 1900
2 PLAC London, England
1 FAMS @F1@
Start by loading that fixture and dumping the parsed records to a log or dd(). Do not attempt a database import on the first pass — confirm that names, dates, places, and @I1@-style pointers resolve the way you expect.
First real use case: a one-off import command (php artisan gedcom:import path/to/file.ged) that reads the file, iterates individuals and families, and writes to your own normalized tables (people, families, events, places). Keep the vendor's output shape out of your schema — map it.
Wrap it, don't leak it. This package sits on a commodity data path that is expensive to replace once it leaks into controllers and blade views. Define an internal interface — e.g. GenealogyImporter — and let the package be one adapter beneath it. When the underlying parser stalls or you outgrow it (see the maintenance notes below), the swap stays inside one class.
Two-stage import: parse → normalize → persist.
@I1@ pointers into foreign keys, split NAME John /Doe/ into given/surname, parse BIRT/DEAT/MARR/DIV into event rows.Doing this in one monolithic loop is where genealogy features rot; the pointer graph (individual → family → parents → children) is genuinely graph-shaped, so give normalization its own layer.
Everything goes through a queued job. GEDCOM 5.5.1 files regularly hit tens of megabytes; parsing them inline will blow PHP memory limits. A ProcessGedcomImport job with the file path on a private disk, chunked progress updates, and a status column on your imports table is the pattern that survives real users. If the underlying parser loads the whole tree, still wrap it in a job so the request cycle is not the failure point.
Idempotency from day one. Genealogy users re-upload after every family discovery. Hash the file (sha256 of contents, not filename) and store that on the import record. On repeat upload, decide explicitly: skip, diff, or merge. Design a stable external identifier (GEDCOM's _UID tag if present, otherwise a deterministic key from name + birth date + place) so re-imports update rather than duplicate.
Handle the pointer graph after parsing. @I1@ → @F1@ → @I2@ relationships are the hard part, and most bugs live here:
FAMS / FAMC on an individual reference families they spouse or child into.HUSB, WIFE, CHIL which reference individuals.Serve reads from your schema, not the package. Once imported, the genealogy package should not be in the read path. Query your own people/families tables with Eloquent; keep the package on the write-only ingress. This isolates you from parser churn and means a library upgrade can't break a list page.
Testing. Keep a small fixture library checked in: a minimal individual, a two-generation family, a file with a _UID custom tag, a BOM-prefixed UTF-8 file, and a Latin-1 encoded file with accented names. Run your import command against each in CI. The v3.4.3 special-character fix is exactly the class of regression these fixtures catch.
Encoding is the headline hazard. GEDCOM files are not reliably UTF-8 — the spec predates it, and many tools emit ANSI/Latin-1, Windows-1252 with smart quotes, or UTF-8 with a BOM. The v3.4.3 release specifically fixes special-character import, but treat that as a starting point, not a guarantee. Before parsing, sniff the encoding (BOM, mb_detect_encoding, or a strict UTF-8 validation check) and transcode to UTF-8 yourself if needed. Store original bytes on disk in case you need to re-run after a code fix.
Name parsing is not trivial. GEDCOM's /surname/ slash convention is required but frequently violated by real exports. John /Doe/, John /Doe/ Jr., John /van der Berg/, and a bare Madonna all occur. Write a defensive splitter and keep the raw NAME string on the row — you will need it for display and debugging.
Date values are ambiguous by design. GEDCOM dates come in forms like 1 JAN 1900, ABT 1900, BET 1900 AND 1910, FROM 1880 TO 1890, @#DGREGORIAN@ 1 JAN 1900, and (estimated). Do not strtotime them. Store the raw string alongside parsed year/month/day/qualifier columns; expect some to be unparseable and render them verbatim.
Case sensitivity and whitespace in tags. INDI is a tag; _UID is a vendor extension (underscore prefix). Some exporters use non-standard casing or trailing spaces. Compare tags case-sensitively per spec but trim aggressively; a single stray space after NAME on line 1 will silently lose data.
Pointer resolution order. If you stream-parse and try to resolve @I1@ references as you encounter them, forward references (HUSB @I2@ on a family listed before I2 appears) will be null. Always do a full parse, then a resolution pass. If you must stream for memory reasons, defer FK writes to a final sweep.
Place strings are freeform. London, England vs London, Middlesex, England vs London, ENG, United Kingdom all coexist within one file. Normalize with a separate address/place parser you control, or keep raw and layer location matching on top — do not attempt it inside the import loop.
Maintenance signal is thin. The last release is January 2023 and the project has 22 stars. Mitigate: pin the exact version in composer.lock, isolate behind the adapter interface above, and check the underlying parser's issue tracker (not just this wrapper's) when encoding bugs surface. Assume you may be the one fixing edge cases eventually.
Config quirks live in the wrapper, not the spec. If there's a config option for the default filesystem disk or driver, publish it early and commit it — the default may be local, which usually won't be where uploads land. Also check whether the package registers a facade; if so, resolve it via the container in tests rather than using Gedcom:: statically, otherwise you'll carry state between test cases.
Extension point worth using: an event/listener pair on your own side (GedcomImported, GedcomImportFailed) so post-processing — search indexing, audit logging, dedup matching — attaches without touching import code. That is where users actually judge the feature, and it should not be tangled with the parser.
How can I help you explore Laravel packages today?