npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gedcom-ts

v2026.6.0

Published

TypeScript GEDCOM toolkit for browser apps: import .ged/.zip, edit typed genealogy data, and export GEDCOM or GEDZIP.

Readme

gedcom-ts

gedcom-ts is a browser-oriented TypeScript library to:

  • import genealogy data from GEDCOM (.ged) or ZIP (.zip / .gdz)
  • work with a typed JSON model (persons, acts, dates, places, notes, media, name variants, attributes)
  • edit the model in-place through a fluent, chainable API (editPerson, editAct, …)
  • export data back to GEDCOM (.ged) or GEDZIP (.zip)
  • attach audio and other media via GEDCOM 7 OBJE records (MEDI AUDIO, multi-FILE, TRAN)
  • model oral history interviews as EVEN acts with OBJE + transcription notes
  • geocode event places (OpenStreetMap / Nominatim) and group similar city names
  • generate a genealogy booklet as PDF (gedcom-ts/booklet, 11 locales including RTL Arabic/Hebrew)

Live demo

A graphical demo showcasing the public API (import a .ged / .zip, browse the typed model, export back) is available at https://gedcomts.com

Contents

Package

  • NPM: gedcom-ts
  • Current release 2026.6.0 — audio / OBJE / oral history. Version format CalVer AAAA.M.micro (e.g. 2026.6.0 = June 2026). See CHANGELOG.md.
  • Entry points:
    • gedcom-ts — import, model, edit layer, export, geocoding
    • gedcom-ts/booklet — PDF livret (pdf-lib en peerDependency, chargé depuis node_modules)

Installation

npm install gedcom-ts

Both entry points come from the same package. For the PDF booklet, pdf-lib is a peer dependency — install it in your app if you use gedcom-ts/booklet:

npm install pdf-lib

For zh / ar / he PDF output, your bundler should also be able to resolve @pdf-lib/fontkit (included in gedcom-ts dependencies; the host app typically preloads it — see Bundle layout below).

Runtime Requirements

  • modern browser runtime (File, Blob, XMLHttpRequest, URL.createObjectURL)
  • for pure Node.js usage, DOM polyfills are required (the library targets browsers)

Quick start

import { importGedFile, ExportGedzipFile } from "gedcom-ts";

async function roundTrip(file: File) {
  const readGed = await importGedFile(file);
  const persons = readGed.persons;

  if (persons.length > 0) {
    persons[0].lastname = persons[0].lastname.toUpperCase();
  }

  await new ExportGedzipFile("updated-tree", persons).download();
}

Typical workflow:

  1. import a file with importGedFile (or start from createEmptyReadGed())
  2. read / mutate the typed Person, Act, Place, Note, MultimediaFile objects (directly or through editPerson / editAct / editPlace / …)
  3. export as .ged (ExportGedcomFile) or .zip (ExportGedzipFile)

Geocoding places

Use this section when your app needs GPS coordinates on event places, a list of cities still without coordinates, map markers, or a data-quality view for inconsistent place names.

You work with a ReadGed (after import) and a flat Act[] built from every person’s events (see Prepare act list).

How city names are matched

The library groups variants of the same place with citiesAreSimilar, not exact string equality.

| Label A | Label B | Same place? | | ----------- | ------------------------------------ | --------------------------- | | Pleurtuit | Pleurtuit, Ille-et-Vilaine, France | Yes | | Paris | Paris, TX, USA | Depends on similarity rules |

Use the functions in the tables below for grouping, geocoding, and maps. Do not compare raw strings yourself, and do not use normalizeCityKey for grouping (spelling normalization only).

Prepare act list

import type { ReadGed, Act } from "gedcom-ts";

function collectAllActs(ged: ReadGed): Act[] {
  const acts: Act[] = [];
  for (const person of ged.persons) {
    for (const act of person.acts.list) acts.push(act);
  }
  return acts;
}

Geocode one city (typical flow)

When the user picks a city and a Nominatim result, update every similar act that still has no GPS — not only acts with the exact same label.

| Step | Function | | ---------------------------------------------- | ----------------------------------------------------------- | | 1. Context from the tree (countries, centroid) | inferGeocodeContext(ged) | | 2. Search OpenStreetMap | searchPlacesWithContext(cityName, context) | | 3. Acts to update | actsNeedingGeocodeForCity(allActs, cityName) | | 4. Write coordinates | applyGeocodeCandidateToActs(targets, candidate, cityName) |

import {
  inferGeocodeContext,
  searchPlacesWithContext,
  actsNeedingGeocodeForCity,
  applyGeocodeCandidateToActs,
} from "gedcom-ts";
import type { ReadGed, Act } from "gedcom-ts";

async function geocodeCity(ged: ReadGed, allActs: Act[], cityName: string) {
  const context = inferGeocodeContext(ged);
  const candidates = await searchPlacesWithContext(cityName, context);
  const chosen = candidates[0];
  if (!chosen) return;

  const targets = actsNeedingGeocodeForCity(allActs, cityName);
  applyGeocodeCandidateToActs(targets, chosen, cityName);
}

Tip: Do not pass only acts from a harmonization cluster to applyGeocodeCandidateToActs. Longer labels (e.g. Pleurtuit, Ille-et-Vilaine, France) would stay without GPS. Always use actsNeedingGeocodeForCity.

List cities missing coordinates

One row per city; withoutCoordCount is how many acts still need GPS.

import { groupActsBySimilarCity } from "gedcom-ts";

const groups = groupActsBySimilarCity(allActs, { onlyWithoutCoordinates: true });

for (const group of groups) {
  console.log(group.cityLabel, group.withoutCoordCount);
  // group.acts — acts in this group without GPS
}

Largest groups first (group.acts.length).

Map: one marker per city

import { clusterKeyForCity } from "gedcom-ts";

const markerKey = clusterKeyForCity(act.place?.city ?? "");
// merge markers that share the same markerKey

Harmonization (data quality, optional)

Use when spellings, GPS positions, or “some acts with / without GPS” disagree for the same similar city.

import { findHarmonizationClusters } from "gedcom-ts";

for (const cluster of findHarmonizationClusters(ged)) {
  console.log(cluster.labels, cluster.coordVariants, cluster.actsWithoutCoord);
}

After a full geocode via actsNeedingGeocodeForCity, you should not get a cluster that only reports missing GPS for that city.

Geocoding API cheat sheet

| Goal | Call | | ------------------------- | ---------------------------------------------------------------- | | Search | searchPlacesWithContext(city, inferGeocodeContext(ged)) | | Acts to update on confirm | actsNeedingGeocodeForCity(acts, city) | | Apply lat/lng | applyGeocodeCandidateToActs(targets, candidate, city) | | Cities without GPS | groupActsBySimilarCity(acts, { onlyWithoutCoordinates: true }) | | Map marker id | clusterKeyForCity(city) | | Inconsistencies | findHarmonizationClusters(ged) |

Network: GET https://nominatim.openstreetmap.org/search?q=…&format=jsonv2 with a User-Agent (gedcom-ts/<version> (genealogy library)). For offline or mocked search, pass fetchFn in searchPlaces / searchPlacesWithContext options.

The legacy callback getCityCoordinates is deprecated — use the flow above.

Genealogy booklet (PDF)

The gedcom-ts/booklet subpath builds a printable family booklet: cover page, table of contents, chapters by generation (Sosa), family sheets, and narrative text from GEDCOM acts (birth, marriage, death, …). English is the default locale (DEFAULT_BOOKLET_LOCALE). Full copy is available for en, fr, de, nl, es, zh, it, pt, pl, ar, and he (aligned with gedcomts.com UI languages). Arabic and Hebrew use RTL layout in the PDF (isBookletLocaleRtl, bookletTextDirection).

Bundle layout

Code is split so the host app only downloads what it uses. Approximate sizes after minify (obfuscation adds ~25–30 KiB on gedcom-ts/booklet).

| Artifact | Size (typ.) | When loaded | | --- | ---: | --- | | gedcom-ts (dist/index.mjs) | ~180 KiB | GEDCOM import / edit / export | | gedcom-ts/booklet (dist/booklet.mjs) | ~100 KiB | livret (collect, estimate, PDF) | | locale-chunks/booklet-locale-* | 14–26 KiB | one UI language | | feature-chunks/booklet-logo-draw | ~11 KiB | cover logo (coverLogo !== false) | | feature-chunks/booklet-timeline-raster | ~3 KiB | timelineStyle: "canvas" | | font-chunks/booklet-pdf-font-bytes-ar | ~43 KiB | Arabic PDF | | font-chunks/booklet-pdf-font-bytes-he | ~13 KiB | Hebrew PDF | | font-chunks/booklet-pdf-font-bytes-zh | ~190 KiB | Chinese PDF (base subset) | | font-chunks/booklet-pdf-font-bytes-zh-ext | ~1.4 MiB | Chinese PDF when GEDCOM names need extra Han | | pdf-lib (peer, not in gedcom-ts) | ~500 KiB+ | any PDF generation |

Not bundled in gedcom-ts: fflate (main entry, external), pdf-lib (booklet peer). bidi-js and naqqash (Arabic shaping / RTL reordering) ship inside gedcom-ts/booklet today.

Call await ensureBookletLocale(locale) before sync APIs. generateGenealogyBookletPdf preloads locale, logo, and timeline when needed.

Lazy chunk subpaths (for bundler preload or explicit import() — normal apps should use ensureBookletLocale() instead):

| Subpath | Role | | --- | --- | | gedcom-ts/booklet/locale-chunks/booklet-locale-{en,fr,de,nl,es,zh,it,pt,pl,ar,he} | Messages, narratives, date/place helpers per locale | | gedcom-ts/booklet/font-chunks/booklet-pdf-font-bytes-{ar,he,zh,zh-ext} | Subset Noto font bytes | | gedcom-ts/booklet/feature-chunks/booklet-logo-draw | Cover logo SVG paths | | gedcom-ts/booklet/feature-chunks/booklet-timeline-raster | Canvas timeline PNG rasterizer |

Each subpath is listed in package.json exports with matching types (.d.ts under dist/booklet/…) and runtime (.mjs / .cjs under dist/…). TypeScript resolves them via exports.types (moduleResolution: bundler / node16). Adding a locale updates scripts/booklet-chunk-manifest.mjs; npm run build:js regenerates chunk files and syncs exports.

Vite / Angular integration

  1. Lazy-load the booklet entryimport('gedcom-ts/booklet') only when the user opens export / download (not on every tree page).
  2. Preload the UI localeimport('gedcom-ts/booklet/locale-chunks/booklet-locale-fr') (or your loader) before sync APIs; then ensureBookletLocale('fr').
  3. Unicode PDF (zh / ar / he) — the host app should import font chunks explicitly and call registerBookletPdfFontBytes + registerPdfFontkit before generateGenealogyBookletPdf (see gedcomts.com booklet-pdf-deps.loader.ts for a reference pattern).
  4. Dev server — exclude the main booklet bundle from Vite pre-bundling so lazy subpaths resolve via node_modules:
// vite.config.ts / angular.json (esbuild optimizeDeps)
optimizeDeps: {
  exclude: ["gedcom-ts/booklet"],
},

Published dist/booklet.mjs keeps literal dynamic import('gedcom-ts/booklet/…') strings (rewrite before obfuscation, paths reserved from string encoding) so Vite can statically analyze chunk loads.

Workflow

  1. Import with importGedFile (gedcom-ts).
  2. await ensureBookletLocale(locale) — load messages/narratives for the chosen language.
  3. Collect persons with collectBookletPersons (gedcom-ts/booklet).
  4. Optionally preview size with estimateBookletSize + groupBookletIntoChapters.
  5. Build bytes with generateGenealogyBookletPdf, then downloadBookletPdf (browser).
import { importGedFile } from "gedcom-ts";
import {
  collectBookletPersons,
  ensureBookletLocale,
  groupBookletIntoChapters,
  estimateBookletSize,
  generateGenealogyBookletPdf,
  downloadBookletPdf,
  personDisplayName,
} from "gedcom-ts/booklet";

async function exportBooklet(file: File) {
  const ged = await importGedFile(file);
  const root = ged.persons[0] ?? null;

  const locale = "en" as const; // BookletLocale — align with your UI language (en, fr, de, nl, es, zh, it, pt, pl, ar, he)
  await ensureBookletLocale(locale);

  const entries = collectBookletPersons({
    ged,
    scope: "from-reference",
    referencePerson: root,
    maxGeneration: 6,
    locale,
  });

  const chapters = groupBookletIntoChapters(entries, locale);
  const families = chapters.reduce((n, ch) => n + ch.families.length, 0);
  const size = estimateBookletSize(chapters, entries.length, families, "summary", "canvas", locale);
  console.log(size.label);

  const pdf = await generateGenealogyBookletPdf(
    entries,
    {
      title: "Family booklet",
      scopeLabel: "Ancestors and descendants",
      personCount: entries.length,
      referenceName: root ? personDisplayName(root) : undefined,
    },
    {
      detailLevel: "summary",
      timelineStyle: "canvas",
      coverLogo: true,
      locale,
    },
  );

  downloadBookletPdf(pdf, "family-booklet.pdf");
}

Use scope: "all" and referencePerson: null to include every individual in the file.

collectBookletPersons options

| Option | Role | | --- | --- | | ged | ReadGed after import | | scope | "all" or "from-reference" (Sosa from referencePerson) | | referencePerson | Root person for "from-reference"; null if scope is "all" | | maxGeneration | Max Sosa generation (e.g. 6); ignored when scope === "all" | | locale | BookletLocale (default "en") — en, fr, de, nl, es, zh, it, pt, pl, ar, he |

Helpers: personDisplayName, buildBookletPersonEntry, sortBookletEntries, bookletSexFromPerson, DEFAULT_BOOKLET_LOCALE, BOOKLET_LOCALES, isBookletLocaleRtl.

generateGenealogyBookletPdf options

| Option | Values | Effect | | --- | --- | --- | | detailLevel | "summary" | "detailed" | Short prose per person vs longer biographies | | timelineStyle | "off" | "canvas" | Generation timeline pages per chapter | | coverLogo | true (default), false, or DrawGedcomTsLogoOptions | gedcom-ts vector logo on the cover | | locale | BookletLocale (default "en") | Narrative, chapter titles, PDF chrome, timeline legend | | unicodeFont | Uint8Array (optional) | Replace bundled Noto font for zh / ar / he |

BookletPdfMeta: title, scopeLabel, personCount, optional referenceName (usually translated in the host app).

Cover logo

The cover draws the gedcom-ts logo from lazy-loaded SVG paths (ensureBookletLogo, getGedcomTsLogoPaths, getGedcomTsLogoViewbox). Reuse on custom PDF pages:

import { PDFDocument } from "pdf-lib";
import {
  drawGedcomTsLogoOnPage,
  defaultCoverLogoOptions,
  ensureBookletLogo,
} from "gedcom-ts/booklet";

const doc = await PDFDocument.create();
const page = doc.addPage();
await ensureBookletLogo();
await drawGedcomTsLogoOnPage(page, defaultCoverLogoOptions());

Booklet API cheat sheet

| Goal | Export | | --- | --- | | Persons for the booklet | collectBookletPersons | | Chapters / families | groupBookletIntoChapters, partnerNamesLabel | | Localized narratives (custom UI) | buildPersonSummaryNarrative, buildPersonDetailedNarratives, buildFamilyNarrative, buildChapterIntroNarrative | | Page estimate | estimateBookletSize, bookletSizeAdvice | | Timeline data / PNG | buildGenerationTimeline, rasterizeGenerationTimelinePng | | PDF output | generateGenealogyBookletPdf, downloadBookletPdf, toPdfText, preparePdfText | | Locale helpers | resolveBookletLocale, bookletLocaleToBcp47, bookletTextDirection, BOOKLET_RTL_LOCALES, ensureBookletLocale | | Font preload (host) | registerBookletPdfFontBytes, registerPdfFontkit, localeNeedsUnicodePdfFont | | Logo | ensureBookletLogo, drawGedcomTsLogoOnPage, getGedcomTsLogoPaths, defaultCoverLogoOptions |

Audio and oral history (GEDCOM 7)

Since 2026.6.0, gedcom-ts models audio according to FamilySearch GEDCOM 7: external files referenced by OBJE records (FILE + FORM + optional MEDI AUDIO + TRAN for alternate formats or WebVTT). Text transcriptions belong in NOTE under the event, not inside OBJE.

Oral history pattern: 1 EVEN + 2 TYPE Interview (or Oral history) + 2 OBJE @O…@ + 2 NOTE (transcription).

import {
  createAudioInterviewAct,
  editAct,
  ExportGedzipFile,
  type Person,
  type ReadGed,
} from "gedcom-ts";

async function addInterview(readGed: ReadGed, person: Person, mp3: File) {
  const objeMap = new Map(readGed.objeRecordsById);

  const { act, objeRecord } = createAudioInterviewAct(
    {
      description: "Entretien avec grand-mère",
      participantIndis: [person.INDI],
      ownerIndi: person.INDI,
      audioFile: mp3,
      transcription: "Bonjour, je m'appelle Marie…",
      vttUri: "media/transcript.vtt", // optional FILE.TRAN on OBJE
    },
    readGed.persons,
    objeMap,
  );

  person.acts.add(act);
  if (objeRecord) {
    objeMap.set(objeRecord.id, objeRecord);
  }

  await new ExportGedzipFile("my-tree", readGed.persons, {
    extraTopLevelRecords: readGed.preservedTopLevelRecords,
    objeRecordsById: objeMap,
  }).download();
}

// enrich an existing act:
editAct(act)
  .asAudioInterview(readGed.persons, objeMap)
  .setTranscription("Suite de l'entretien…")
  .attachAudio({ audioUri: "https://example.org/rec.mp3", ownerIndi: person.INDI });

| Goal | API | | --- | --- | | Create interview act | createAudioInterviewAct | | Attach audio to act | attachAudioInterviewToAct, editAct(act).asAudioInterview() | | Detect imported interviews | isAudioInterviewAct, isAudioInterviewEvenType | | Read transcription / media | getAudioInterviewTranscription, primaryAudioInterviewUri | | OBJE round-trip | readGed.objeRecordsByIdobjeRecordsById in export options | | Low-level OBJE parse/emit | parseStandaloneObjeBlock, formatObjeRecordBlock, guessMediFromForm | | EVEN type constants | GEDCOM_7_EVEN_TYPE_AUDIO_INTERVIEW, GEDCOM_7_EVEN_TYPE_ORAL_HISTORY |

API reference

Short description and a minimal snippet for each export of gedcom-ts. For the PDF booklet, see Genealogy booklet (PDF) (gedcom-ts/booklet).

Importing a file

importGedFile(file: File): Promise<ReadGed>

Detects the format from the file MIME / extension and dispatches to the right reader.

  • accepts a single .ged file
  • accepts a .zip / .gdz archive containing one .ged + optional media files
  • throws Error(IMPORT_ERR_ZIP_GED_UNREADABLE) when the embedded .ged cannot be decoded (typical cause: password-protected ZIP)
import { importGedFile } from "gedcom-ts";

const readGed = await importGedFile(fileInput.files![0]);
console.log(readGed.persons.length, readGed.datasetVersion);

createEmptyReadGed(options?): ReadGed

Creates an empty graph with the same runtime shape as a successful import (empty persons, initialized mapPersons / partnersMap / childsMap / placesMap / mapFiles). Use it to start a brand-new tree without parsing a file. datasetVersion is set to "7.0".

import { createEmptyReadGed } from "gedcom-ts";

const readGed = createEmptyReadGed();

IMPORT_ERR_ZIP_GED_UNREADABLE: string

Sentinel error message thrown by importGedFile when a .ged inside a ZIP cannot be decoded (typically because the ZIP is password-protected). Compare with error.message === IMPORT_ERR_ZIP_GED_UNREADABLE to display a tailored message.

import { importGedFile, IMPORT_ERR_ZIP_GED_UNREADABLE } from "gedcom-ts";

try {
  await importGedFile(file);
} catch (error) {
  if (error instanceof Error && error.message === IMPORT_ERR_ZIP_GED_UNREADABLE) {
    alert("Please unzip the archive manually and import the .ged file.");
  }
}

ReadGed

Result of an import. Standalone 0 @O…@ OBJE blocks with at least one FILE are parsed into objeRecordsById (multi-file, MEDI, TRAN). Other top-level records (REPO, SOUR, SUBM, …) stay on preservedTopLevelRecords for round-trip export.

Notable members:

| Member | Description | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | persons: Person[] | Imported individuals. | | mapPersons: Map<number, Person> | INDI (integer) → Person. | | partnersMap: Map<number, Person[]> | Family id → spouses. | | childsMap: Map<number, Person[]> | Family id → children. | | placesMap: Map<string, Place> | City name → Place (first occurrence wins). | | mapFiles: Map<string, File> | Relative path → media File (for ZIP imports). | | datasetVersion: GedcomDatasetVersion | "7.0", "5.5" or "unknown". | | objeRecordsById: Map<number, ObjeRecord> | Parsed OBJE records (audio, images, …) keyed by @O{n}@. | | preservedTopLevelRecords: string[] | Raw blocks for partial round-trip. | | resolveIndividualPointer(raw) | Resolves @I12@, I12, @Homer_Simpson@, Homer_Simpson to a Person. | | getChildrenForParent(parent) | All children attached to a parent (via FAMS). | | getChildrenOfFamily(familyId) | Children of a single family. | | groupPartners() | Rebuilds partnersMap / childsMap after editing links. | | generateUniqueIndi() | Next free INDI number for new persons. | | rehydratePlacesFromActs() | Rebuilds placesMap from act places (e.g. after manual graph edits). Use editReadGed(readGed).addPerson(...) to register persons so maps stay coherent. |

import { ReadGed } from "gedcom-ts";

function describe(readGed: ReadGed) {
  return {
    version: readGed.datasetVersion,
    persons: readGed.persons.length,
    places: readGed.placesMap.size,
  };
}

Exporting

ExportGedcomFile

Writes a GEDCOM 7 (.ged) file. Three call signatures are supported:

new ExportGedcomFile(persons);
new ExportGedcomFile(title, persons);
new ExportGedcomFile(title, persons, options);

.download() triggers a browser download. .toString() returns the GEDCOM text.

import { ExportGedcomFile, type ReadGed } from "gedcom-ts";

function exportGed(readGed: ReadGed) {
  new ExportGedcomFile("my-tree", readGed.persons, {
    extraTopLevelRecords: readGed.preservedTopLevelRecords,
    objeRecordsById: readGed.objeRecordsById,
    headLanguageTag: "fr-FR",
    headCopyright: "© 2026 Family Archive",
    headDestination: "https://gedcom.io/",
    headSchemaTagDefs: [{ tag: "_FOO", uri: "https://example.com/foo" }],
  }).download();
}

ExportGedzipFile

Writes a .zip (GEDZIP) bundling the GEDCOM and all attached MultimediaFile payloads.

import { ExportGedzipFile, Person } from "gedcom-ts";

async function exportZip(persons: Person[]) {
  await new ExportGedzipFile("my-tree", persons).download();
}

GedcomExportOptions

Options shared by both exporters:

| Option | Effect | | ---------------------- | -------------------------------------------------------------------------------------------------------- | | extraTopLevelRecords | Raw 0 … blocks re-emitted before SUBM / TRLR (round-trip with readGed.preservedTopLevelRecords). | | objeRecordsById | Full OBJE records (multi-FILE, MEDI AUDIO, TRAN) from readGed.objeRecordsById. | | headLanguageTag | BCP 47 tag for HEAD.LANG (defaults to en-US). | | headCopyright | One-line 1 COPR notice. | | headDestination | Value of HEAD.DEST (target app / URI). | | headSchemaTagDefs | Extension tag definitions emitted as HEAD.SCHMA / 2 TAG …. |

Domain model

Person, Sex

import { Person, Sex } from "gedcom-ts";

const person = new Person();
person.INDI = 1;
person.SEX = Sex.M; // M | F | U | X
person.firstnames = ["Jean"];
person.lastname = "DUPONT";

person.addMultimedia(/* MultimediaFile */);
person.deleteMultimedia("1/photo.jpg");

Key fields: INDI, sosa, SEX, firstnames, lastname, FAMC, FAMS, acts, notes, multimediaFiles, nameVariants, attributes.

PersonGedcomImportOptions (type)

Optional hints consumed by Person.createPersonJson when re-parsing a single individual block (label-keyed pointers for FAMS / FAMC / NOTE, and standalone OBJE payloads). Useful when assembling a graph manually outside ReadGed.

PersonNameVariant, PersonNameTranslation

Lossless representation of every 1 NAME block of an individual (type, NPFX/GIVN/SURN/… parts, TRAN translations). Person.nameVariants keeps them in order so alias / translation data survive an import → export round-trip.

import { PersonNameVariant, PersonNameTranslation } from "gedcom-ts";

const variant = new PersonNameVariant("Jean /Dupont/", "BIRTH");
variant.parts.push({ level: "2", tag: "GIVN", value: "Jean" });
variant.translations.push(new PersonNameTranslation("ジャン /デュポン/", "jp"));

IndiAttribute, IndiGedcomSubLine

Generic level-1 individual attributes (FACT, DSCR, CAST, EDUC, OCCU, RELI, TITL, RESN, …) with their sub-lines preserved (IndiGedcomSubLine = { level; tag; value }).

import { IndiAttribute } from "gedcom-ts";

const occ = new IndiAttribute("OCCU", "Blacksmith", [
  { level: "2", tag: "DATE", value: "1820" },
]);

Act, Acts, TypeAct, ActConstructionOptions

Act models an individual or family event (BIRT, MARR, etc.). Acts is the ordered collection on a Person. TypeAct is the union of every supported GEDCOM 7 event tag (= Gedcom7EventTag). Event-level notes live on act.notes (2 NOTE / 3 CONT in GEDCOM); they are distinct from person.notes (1 NOTE on INDI) and round-trip through ZIP export/import.

import { Act, Acts, Identifier, type TypeAct } from "gedcom-ts";

const acts = new Acts();
const type: TypeAct = Identifier.BIRT;
acts.add(new Act(type));
acts.sortByDate();

ActConstructionOptions controls the extras when building an Act manually:

import { Act, DateAct, Identifier } from "gedcom-ts";

const act = new Act(
  Identifier.EVEN,
  new DateAct("12 JAN 1901"),
  null,
  null,
  null,
  undefined,
  undefined,
  {
    evenDescription: "Won a medal",
    evenTypeLabel: "Award",
    sdateAct: new DateAct("13 JAN 1901"),
    eventPhrases: ["family gathering"],
    preservedSubrecordPrefix: [],
    preservedSubrecordSuffix: [],
  },
);

GEDCOM_7_ALL_EVENT_TAGS, GEDCOM_7_EVENT_SORT_ORDER, GEDCOM_7_EVENT_TAG_SET, GEDCOM_7_PAIR_UNION_EVENT_TAGS, Gedcom7EventTag, Gedcom7PairUnionEventTag

Canonical lists of GEDCOM 7 event tags (INDIVIDUAL_EVENT_STRUCTUREFAMILY_EVENT_STRUCTURE, LDS ordinances excluded). Use them to build UI selects or guard custom logic. GEDCOM_7_PAIR_UNION_EVENT_TAGS lists tags commonly used when creating a family union via createMarriageFamily (ENGA, MARB, MARC, MARL, MARR, MARS).

import {
  GEDCOM_7_ALL_EVENT_TAGS,
  GEDCOM_7_EVENT_SORT_ORDER,
  GEDCOM_7_EVENT_TAG_SET,
  type Gedcom7EventTag,
} from "gedcom-ts";

const options: Gedcom7EventTag[] = [...GEDCOM_7_ALL_EVENT_TAGS];
const isEvent = GEDCOM_7_EVENT_TAG_SET.has("MARR");
const sorted = [...GEDCOM_7_EVENT_SORT_ORDER];

DateAct, Day, Month, dateToDateLine, days, months, TypeDateActSimpleQualifier

GEDCOM dates with full GEDCOM 7 / 5.5 support, including INT, EST, CAL, BET … AND …, FROM … TO …, ISO YYYY-MM-DD, 3 PHRASE under DATE, 3 TIME, and a verbatim fallback for unparseable payloads.

import {
  DateAct,
  Day,
  Month,
  dateToDateLine,
  days,
  months,
} from "gedcom-ts";

const day: Day = 12;
const month: Month = months[0]; // JAN
const dateLine = dateToDateLine(1901, month, day); // "12 JAN 1901"
const dateAct = new DateAct(dateLine);
const formattedDate = dateAct.date;
const knownDaysCount = days.length;

TypeDateActSimpleQualifier is the union of single-anchor qualifiers (BEF | ABT | AFT | INT | EST | CAL) accepted by DateAct.updateQualifiedDate and by DateActEdit.setQualified.

Place, CoordinateGPS

import { Place, CoordinateGPS } from "gedcom-ts";

const place = new Place("Paris, FR", new CoordinateGPS(48.8566, 2.3522));
place.setFromGedcom7PlacPayload("Paris, Île-de-France, France");
const payload = place.toGedcom7PlacPayload(); // "Paris, , Île-de-France, France"

Place understands the City, County, State, Country GEDCOM 7 list (1 to 4+ segments) and exposes placPhrase for 3 PHRASE under PLAC (import + export).

MultimediaFile, MultimediaFiles

Wraps either a local File, an external URI (sourceUri) or an OBJE pointer (objeXrefId) so the same model can survive a GED / ZIP round-trip. See Audio and oral history for interview workflows.

import { MultimediaFile, MultimediaFiles } from "gedcom-ts";

const bucket = new MultimediaFiles();
bucket.relativePath = "1/BIRT";
bucket.add(new MultimediaFile(new File(["img"], "birth.jpg")));

const remote = new MultimediaFile(undefined, "https://example.com/p.jpg");
const isExportable = remote.hasExportablePayload();

Note, Notes, TypeNote

import { Notes, Note, Identifier, type TypeNote } from "gedcom-ts";

const typeNote: TypeNote = Identifier.CONT;
const note = new Note();
note.updateType(typeNote);
note.updateLines(["first line", "second line"]);

const notes = new Notes();
notes.addNote(note);
notes.removeFromIndex(0);

Identifier (and deprecated Identificator)

Enum of every GEDCOM tag the library refers to (INDI, BIRT, MARR, DATE, PLAC, …). Prefer Identifier; Identificator is kept as a deprecated alias.

import { Identifier } from "gedcom-ts";

const birthTag = Identifier.BIRT;

EventsByYears, ActsByYear

Group acts by year, deduplicating per individual. Handy to build chronological timelines.

import { EventsByYears, ActsByYear } from "gedcom-ts";

const grouped = new EventsByYears(person.acts.list);
for (const bucket of grouped.events) {
  console.log(bucket.year, bucket.list.length);
}

const yearBucket = new ActsByYear(1901);

Edit layer (in-place, chainable)

Wrap an existing model object to mutate it with a fluent API. Every method returns this, so you can chain. .value exposes the underlying target.

import { editPerson, editDateAct, DateAct } from "gedcom-ts";

editPerson(person)
  .setLastname("Dupont")
  .setFirstnames(["Jean", "Marie"])
  .acts()
  .at(0)
  .setDateAct(new DateAct("1 JAN 1900"));

editDateAct(person.acts.list[0].dateAct!).setExactDate(1900, "JAN", 1);

| Helper / class | Purpose | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | editPerson(person) / PersonEdit | Identity, FAMS / FAMC / removeFams*, nameVariants() / attributes() / multimedia(), bulk clear*, entry points to acts() and notes(). | | editActs(acts) / ActsEdit | add, addNew, insertAt, insertNewAt, replaceAt, removeAt, removeAct, removeLast, clear, sortByDate, at, indexOfAct. | | editAct(act) / ActEdit | setType, setIndis, dates, place, EVEN fields, preserved lines, notes(), multimedia(), asAudioInterview(), clearNotes, clearMultimedia. | | editAudioInterviewAct(act) / AudioInterviewActEdit | Oral-history facade: attachAudio, setTranscription, setDescription, registerPendingObje, isAudioInterview, … | | editDateAct(dateAct) / DateActEdit | clear, applyGedcomPayload, setExactDate, setQualified, setBetween, setFromTo, setTime, setDatePhrase / appendDatePhrase, setVerbatimPayload. | | editPlace(place) / PlaceEdit | setFromGedcom7Payload, setCity, setCounty, setState, setCountry, setPlacPhrase / appendPlacPhrase / clearPlacPhrase, setCoordinates / clearCoordinates / replaceCoordinateModel, clearStructured. | | editNotes(notes) / NotesEdit | add, addNew, insertAt, insertNewAt, replaceAt, removeAt, removeNote, removeLast, clear, at, indexOfNote. |

Dataset editing: ReadGed, graph, clone, export options, validation

Higher-level helpers complement the per-object edit facades:

import type { GedcomExportOptions } from "gedcom-ts";
import {
  createEmptyReadGed,
  createPersonStub,
  DateAct,
  Sex,
  editReadGed,
  editGedcomExportOptions,
  clonePerson,
  cloneAct,
  createMarriageFamily,
  linkChildToFamily,
  removeFamilyReferencesFromDataset,
  addPersonToReadGed,
  validateReadGed,
  Identifier,
  Act,
} from "gedcom-ts";

const readGed = createEmptyReadGed();
const p = createPersonStub(readGed.generateUniqueIndi(), { sex: Sex.M, firstnames: ["Jean"], lastname: "Dupont" });
editReadGed(readGed).addPerson(p);
p.acts.add(new Act(Identifier.BIRT, new DateAct("1900")));

editReadGed(readGed).preserved().append("0 @S42@ SOUR Custom");

const opts: GedcomExportOptions = {};
editGedcomExportOptions(opts)
  .setHeadCopyright("© 2026")
  .setExtraTopLevelRecords([...readGed.preservedTopLevelRecords]);

const twin = clonePerson(p, readGed.generateUniqueIndi());
const birthCopy = cloneAct(p.acts.list[0]!);

// const fam = createMarriageFamily(readGed, spouseA, spouseB, { dateAct: new DateAct("1 JAN 2000") }, { eventTag: Identifier.ENGA });
// linkChildToFamily(readGed, child, fam);
// removeFamilyReferencesFromDataset(readGed, fam);

validateReadGed(readGed, {
  checkMarrParticipants: true,
  checkFamcWithoutSpouses: true,
  checkFamsWithoutSpouses: true,
  checkDuplicateFamsEntries: true,
  checkAncestorCycles: true,
});

Commands (invariants before mutation)

tryAddPersonToReadGed, tryRemovePersonFromReadGedByIndi, tryLinkChildToFamily, tryUnlinkChildFromFamily, and tryCreateMarriageFamily return a CommandResult (ok + issues or value + optional warnings). editReadGed(...).addPerson uses these checks internally (throws on blocking errors). For UI or transactional flows, prefer the try* APIs and inspect commandBlockingIssues.

| Export | Role | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | editReadGed(readGed) / ReadGedEdit | addPerson, removePersonByIndi, preserved()PreservedTopLevelEdit (append, insertAt, replaceAt, removeAt, clear) on preservedTopLevelRecords. Low-level helpers: addPersonToReadGed, removePersonFromReadGedByIndi. | | editGedcomExportOptions(opts) / GedcomExportOptionsEdit | Fluent setters for extraTopLevelRecords, objeRecordsById, headLanguageTag, headCopyright, headDestination, headSchemaTagDefs. | | clonePerson(person, newIndi) / cloneAct(act) / person.clone / act.clone | Deep copies for templates or undo stacks. | | nextFamilyId(persons) | Next internal family id F. | | createMarriageFamily, linkChildToFamily, unlinkChildFromFamily, removeFamilyReferencesFromDataset | Create family F and spouse acts; optional { eventTag } (default MARR, also ENGA, banns, contract, EVEN + CreateActInit, …). See GEDCOM_7_PAIR_UNION_EVENT_TAGS. | | validateReadGed, assertReadGedConsistent, validatePerson, assertPersonConsistent | Typed code / severity (error | warn). Options: checkMarrParticipants, checkDuplicateIndis, checkFamcWithoutSpouses, checkFamsWithoutSpouses, checkDuplicateFamsEntries, checkAncestorCycles. Assertions: failOn (default: error). | | tryAddPersonToReadGed, tryRemovePersonFromReadGedByIndi, tryLinkChildToFamily, tryUnlinkChildFromFamily, tryCreateMarriageFamily, commandBlockingIssues | Command layer with CommandResult / validate*Command prechecks. |

Utilities

createSosaMap(root, partnersMap)

Computes a Sosa numbering (Ahnentafel) starting at root (Sosa 1) and recursively walking ancestors via partnersMap.

import { createSosaMap, Person } from "gedcom-ts";

const sosaMap = createSosaMap(root, readGed.partnersMap);
// e.g. sosaMap.get(root.INDI) === 1

remainingTypesAct(acts, actToUpdate?)

Returns the list of event types still allowed for a person’s Acts, enforcing the “unique per individual” rule for BIRT / DEAT / BURI / CHR while keeping every other tag selectable. Pass the currently edited act as actToUpdate so its own type stays in the list when re-opening a form.

import { remainingTypesAct, Acts } from "gedcom-ts";

const available = remainingTypesAct(new Acts());

getCityCoordinates(cityName, callback) (deprecated)

Legacy callback API. Use Geocoding places instead.

resolveDatasetVersion(headerLines) / GedcomDatasetVersion

Inspects the lines of a 0 HEAD block and returns "7.0", "5.5" or "unknown". Useful to branch UI behaviour for legacy datasets.

import { resolveDatasetVersion } from "gedcom-ts";

const version = resolveDatasetVersion(headLines); // "7.0" | "5.5" | "unknown"

guessMediaFormFromUri(uri)

Best-effort media type detection from a path/URI or local File (file.type when set). Covers images, audio (mp3, webm, ogg, wav, m4a, flac, …), video, WebVTT, PDF. Used to fill OBJE.FILE.FORM. Pair with guessMediFromForm for MEDI (AUDIO, PHOTO, …). refineGenericObjeForm upgrades legacy application/octet-stream exports using the file extension.

import { guessMediaFormFromUri, guessMediaFormFromFile } from "gedcom-ts";

guessMediaFormFromUri("media/recording.webm"); // "audio/webm"
guessMediaFormFromFile(new File(["x"], "take.webm", { type: "audio/webm" })); // "audio/webm"

extractPersonNameVariants(personLines) / extractIndiAttributes(personLines)

Low-level parsers used by Person.createPersonJson. They turn the raw GEDCOM lines of a single INDI record into structured PersonNameVariant[] / IndiAttribute[]. Reuse them when parsing custom GEDCOM fragments outside ReadGed.

import { extractPersonNameVariants, extractIndiAttributes } from "gedcom-ts";

const variants = extractPersonNameVariants(rawIndiLines);
const attributes = extractIndiAttributes(rawIndiLines);

selectPrimaryNameVariant(variants)

Picks the most relevant 1 NAME block: priority to 2 TYPE BIRTH, then to a name with a /surname/ payload, otherwise the first variant.

import { selectPrimaryNameVariant } from "gedcom-ts";

const primary = selectPrimaryNameVariant(person.nameVariants);

GEDCOM_LIBRARY_VERSION

CalVer string written in exported HEAD.SOUR.VERS (same value as the npm package version, e.g. 2026.6.0).

import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";

console.log(`gedcom-ts ${GEDCOM_LIBRARY_VERSION}`);

Error handling

import { importGedFile, IMPORT_ERR_ZIP_GED_UNREADABLE } from "gedcom-ts";

try {
  const readGed = await importGedFile(file);
  console.log(readGed.persons.length);
} catch (error) {
  if (error instanceof Error && error.message === IMPORT_ERR_ZIP_GED_UNREADABLE) {
    console.error("Encrypted ZIP: please extract the .ged manually.");
  } else {
    console.error("GED import failed:", error);
  }
}