cgdprojects/genealogy
Laravel package for genealogy apps: manage people, families, events, and relationships; import/export GEDCOM; query ancestors and descendants; and render family trees with Eloquent models and Blade views.
This is a Laravel 10 / PHP 8.2 application, not a composer require-able library. The most important step is correcting the mental model before you start: you clone or fork the repo, you don't install it into something else.
Minimal steps:
git clone https://github.com/cgdprojects/genealogy.git
cd genealogy
composer install
cp .env.example .env
php artisan key:generate
# set DB_* in .env (MySQL/Postgres), then:
php artisan migrate --seed
php artisan serve
Look in this order:
composer.json / README.md — confirm the Laravel/PHP constraint and any required extensions (GEDCOM parsing typically wants mbstring, intl).database/migrations/ — this is the real spec. The domain model (individuals, families, events, relationships, places, sources/citations) is defined here, not in prose docs.routes/ + app/Http/Controllers/ — the actual supported workflows.app/Models/ — relationships, scopes, and accessors you'll be extending.database/seeders/ + factories/ — the fastest way to see a populated tree.First use case: stand up a working tree. Create individuals, create a family unit, attach parent/child and spouse links, add events (birth, death, marriage, residence), then render a pedigree or descendant chart. That end-to-end path exercises the models, the relationship layer, and whatever rendering/API layer exists — it will tell you within an hour whether you're adapting this or lifting pieces out of it.
Before writing any code, decide fork vs. cannibalize:
app/Models, the migrations, and the import/export code into your own Laravel app. Given the archive status, this is usually the better call.Treat the repo as a reference implementation. The highest-leverage pattern is extracting the domain layer into your own project rather than building on top of someone else's abandoned app skeleton. Copy models + migrations + service classes together so foreign keys and enums stay consistent.
Model the relationship layer explicitly, don't shortcut it. Genealogy data does not fit person.parent_id. The workable pattern is the GEDCOM-inspired one the schema almost certainly follows:
individuals — the person, with name parts stored separately (given/surname/prefix/suffix) because sorting and matching depend on it.families — a couple/union record, not a person.family_individual (or a relationships pivot) — links individuals to families with a role (husband, wife, child) plus a qualifier for adoption/foster/step.events — polymorphic or dual-FK, attached to either an individual or a family. Birth belongs to a person; marriage belongs to a family.This shape is what lets you support half-siblings, second marriages, and adoptive parents without schema surgery later.
Tree traversal as a dedicated service, not model methods. Ancestor/descendant queries are the performance-critical path. Two viable approaches:
// Small trees: in-memory BFS with eager-loaded pivots
$tree = app(PedigreeBuilder::class)->ancestors($individual, depth: 5);
// Large trees / Postgres: recursive CTE
DB::select("WITH RECURSIVE anc AS (...) SELECT ...");
Keep traversal behind an interface so you can swap strategies without touching controllers.
Wrap GEDCOM import/export in queued jobs. GEDCOM files are multi-megabyte text; parsing them inline will time out. The pattern that works:
ImportGedcom::dispatch($path, $userId); // parse -> normalize -> upsert in chunks
Parse into an intermediate DTO layer first, validate, then persist. Direct-to-Eloquent importers produce half-written trees when a malformed record appears on line 40,000.
Normalize on the way in, not on the way out. Genealogy dates are the classic trap: ABT 1850, BEF 12 MAR 1900, BET 1880 AND 1890, Julian/Gregorian dual dates. Store a normalized range (date_from, date_to, date_qualifier) alongside the raw string. Same for place hierarchies — split "Boston, Suffolk, Massachusetts, USA" into components at import.
Privacy as a policy layer. Living individuals are the GDPR problem. Add an is_living flag (computed from death events and a birth-date threshold, e.g. 110 years) and enforce visibility via a Laravel policy plus a global scope on the model. Doing this retroactively across an existing tree is painful; do it on day one.
Use seeders/factories for a fixture tree. Build a factory that generates a deterministic 4-generation tree. It makes tree-rendering bugs and traversal regressions reproducible, and it's what you'll use in every test.
API integration tip: if you expose this to a SPA or mobile client, put a PersonResource / FamilyResource in front of the models immediately. Genealogy models accrete fields (sources, citations, media, notes) fast, and you do not want raw Eloquent serialization leaking to clients.
Archived — 2023-07-12 is the last release. This is the dominant risk. It's pinned to Laravel 10 and PHP 8.2, both of which will fall out of security support. There will be no patches. Assume you own the codebase the moment you adopt it, and budget for a Laravel upgrade pass (10 → 11 → 12) as part of your initial integration, not as a later chore. Fork it into your own namespace so upgrades are yours to control.
It's an application, not a package. There is no SemVer, no upgrade guide, no extension contract. Anything you build against its internals is a private API. Keep your customizations in separate classes/services that call into the domain layer rather than editing models in place — otherwise your next merge is a manual diff of doom.
Strip the shell early. App-shaped repos come with opinionated auth, admin panels, and frontend scaffolding you may not want. Deleting unused pieces after you've built on them is far harder than deleting them on day two.
N+1 queries on tree traversal are the default failure mode. Rendering a 6-generation pedigree naively issues hundreds of relationship queries. Eager-load (with('families.individuals')) or pre-fetch the whole subtree into memory before rendering. Profile with DB::listen or Telescope on a seeded 500-person tree before you trust it.
Genealogy edge cases that break naive schemas: unknown/illegitimate parents, same-sex unions, multiple simultaneous spouses, name changes after marriage, individuals appearing in several families, and dates that are ranges or estimates. If the schema has a nullable father_id/mother_id pair, you will hit a wall — check this first, because it determines whether you're adapting or replacing the core.
GEDCOM encoding quirks. Files arrive in ANSI, UTF-8, and UTF-16 variants, with CONC/CONT continuation lines that must be joined before parsing, and custom _-prefixed tags. Validate against real exports from Ancestry/MyHeritage/FamilySearch — each has quirks. Wrap the parser so a single bad record logs and skips rather than aborting the whole import.
Config quirks: you'll likely need to touch config/app.php locale/timezone and any genealogy-specific config file (date formats, name display order, privacy thresholds). Locale affects name and date parsing directly — test import with non-English data if you have any.
Debugging tips: php artisan tinker against a seeded tree is the fastest way to sanity-check traversal logic. For import bugs, keep the raw GEDCOM line number on each parsed DTO and surface it in error messages — "record 4213, line 18822" saves hours.
Extension points to preserve: the GEDCOM parser, the name/date normalizers, and the tree traversal service. These are the three places you will genuinely want to swap implementations. Isolate them behind interfaces early; everything else in the app you can change freely.
Score context: opportunity 5.26 vs. overall 3.72 reflects a genuinely useful domain (genealogy is underserved in the Laravel ecosystem) wrapped in an abandoned, app-shaped delivery. Use it as a schema blueprint and a source of validated domain logic; don't inherit its long-term maintenance burden.
How can I help you explore Laravel packages today?