1 - Enhanced Links

Cross-references and linkenhancer links in GEDCOM records

Although webtrees replaces XREFs such as @I2@ in notes with a cross-reference and its appropriate display name, this is not very flexible. You can’t determine the display name, which means that in the event of an error (due to XREF renumbering or typing error), it is not easy or possible to resolve the cross-reference to the corresponding data record without any doubt.

Tip: The custom module webtrees-mitalteli-show-xref can help you find the XREF and UID reference of personal data records. Some UIDs (38 characters length) are automatically shortened by webtrees in the standard view — this module shows the unchanged UIDs.

How it works

The enhanced links function is implemented via JavaScript and searches in the rendered output of webtrees for <a> tags whose href attribute values begin with #@. This way it is possible to handle links in markdown and HTML markup. Because it is processed in the browser on the client side, the existence of the cross-referenced data records in webtrees is not checked in advance. Errors only occur when the link is clicked (e.g. if the access to the resource is restricted).

As a bonus, different destinations can be addressed with one link definition, whereby the cross-reference is always the first link (if set) and the others are represented by attached clickable icons only.

Predefined external targets

The parameter keys are listed in parentheses:

Each parameter key can be specified multiple times — but you shouldn’t overdo it for the sake of clarity.

Additional external destinations can be configured on the Admin page.

Syntax

Markdown: An enhanced link looks like this [Link display title](#@param1&paramN), so that one or more targets can be addressed at once. For cross-references in webtrees a record reference (REF can be the XREF or a UID) is expected:

  • wt=@REF@ or wt=@REF@othertree — standard link, access to record page via redirect URL
  • wt=n@REF@ — legacy link with given record type (here: to a note with REF in the current tree)
    • Available record types: i=individual, f=family, s=source, r=repository, n=note, l=sharedPlace
  • wt=i@REF@othertree+dia — link to record type individual with REF from tree “othertree” and also link to Interactive tree of this person

HTML: The same applies to HTML links: <a href="#@wt=i@I1@">Link display title</a>

This is also usable in cooperation with the name badge function of the Vesta Classic Look & Feel module in the HTML snippet field: <a href="#@fsft=<ref/>"></a> for linking to a record in the Family Search Family Tree. It’s also possible to embed those links in news articles of Tywed’s news-menu module or in FAQ articles.

The syntax of the external targets is listed by the markdown help function of this module. In most cases, only one key-value parameter pair needs to be specified, consisting of the short name of the desired target and the ID of the data record located there.

Example

In the upper part of the image we can see the edit mode of a note record, below how it is rendered in view mode:

Note with enhanced links - edit and view

1.1 - Cross-Reference Overview

Cross-reference overview with link index for large trees

There are three types of overviews:

  1. overview about all data for the admin (admin overview),
  2. to one tree limited overview (user overview), and
  3. record-specific overview on the record details view (record overview, as tab or modal).

The admin overview (Control panel → LinkEnhancer → Cross-reference overview) is a server-side paginated DataTable. It lists all records that contain classic @XREF@ cross-references or linkenhancer links, with a per-record link inventory.

Optional filters:

  • referenced XREF (only available while a fresh link index is active — in live-scan mode the field is hidden, because it would only be a coarse pre-filter there — no classic references are found),
  • record type,
  • tree, and
  • the number of tokens shown per link class (all / 5 / 10 / 20, default 5).

The XREF cell shows the tree name as a second muted line; the type is its own sortable column. Each inventory token is displayed as a short display context (snippet) with the link token itself in bold.

Admin overview

Optionally you can enable a user overview per tree and a record overview with incoming and outgoing references per GEDCOM record. The record overview also displays the XREF and, if applicable, the UIDs of the record, so that these references can be more easily copied into notes.

Record overview

Supported engines

Live overviewLink index (CLI)
MariaDB / MySQLyes (REGEXP gate)yes
PostgreSQLyes (REGEXP gate)yes
SQLite / SQL Serverlimited mode — automatic fallback to a coarse LIKE gate + full table scan per request (banner on the page); the exact classification still happens in PHPnot available (needs REGEXP + MD5) — the CLI exits with an error

How it works

On large trees the live regex scan over the GEDCOM tables can be slow, so the page can instead read a link index made of three module tables (the Developer / Project Structure section contains more details about the database schema):

  • le_record_scan — one row per scanned record with an MD5 fingerprint of the record text
  • le_link_index — one row per (link, target) found in the record (the display context captured at build time) — the foundation for displaying incoming cross-references in the record overview
  • le_index_meta — single row — the state of the last complete index run
flowchart TD
    CLI["🛠 cli/build-link-index.php<br/>Cron: */30 * * * *"]

    subgraph Scan["🔍 Incremental scan"]
        FP["MD5 Fingerprint<br/>per record"]
        DIFF{"🔀 Changed?<br/>(new / removed)"}
    end

    subgraph Tables["💾 Tables"]
        RS[("le_record_scan<br/>file, xref, hash")]
        LI[("le_link_index<br/>token, target, snippet")]
        META[("le_index_meta<br/>last_run, rows")]
    end

    PAGE["📄 XREF Overview Page"]

    CLI --> Scan
    FP --> DIFF
    DIFF -->|✅ yes| SCAN["PHP Scan<br/>(XrefsService)"]
    DIFF -->|⏭ no| SKIP["skip"]
    SCAN --> RS
    SCAN --> LI
    CLI --> META

    META -->|"🟢 fresh (< 2 h)"| PAGE
    META -->|"🟠 stale"| LIVE["Live Regex Scan"]
    LIVE --> PAGE

    classDef scanStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px
    classDef tablesStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px
    classDef decideStyle fill:#fff3e0,stroke:#E65100,stroke-width:2px
    classDef pageStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px

    class FP scanStyle
    class RS,LI,META tablesStyle
    class DIFF decideStyle
    class PAGE pageStyle

The index is maintained by build-link-index.php :

  • Initial build (first run, or --rebuild): both index tables are reset, all scan rows are created server-side in one bulk statement per GEDCOM table (fast, zero row transfer), and only the records that pass the link pre-filter are scanned in PHP. The initial build is a one-time cost — use a higher --limit for it. If it is interrupted by --limit, the next run converges (the build is idempotent).
  • Incremental runs (the normal cron case): only records whose MD5 fingerprint changed — or which were added/removed — are re-scanned (cursor chunked), so the job stays cheap even on large sites.

Cron example (every 30 minutes, up to 2000 records per run):

*/30 * * * * cd /path/to/webtrees && php modules_v4/linkenhancer/cli/build-link-index.php --limit=2000 >> /var/log/linkenhancer-index.log 2>&1

Only a full run (without --tree) that is not cut by --limit marks the index fresh (le_index_meta.last_run). A run cut by --limit continues where it stopped; a --tree run only refreshes that tree.

Flushing

--flush empties the index tables and exits (no rebuild) — for testing (force the page onto the live scan) and maintenance (clean restart, broken index):

php modules_v4/linkenhancer/cli/build-link-index.php --flush            # all trees
php modules_v4/linkenhancer/cli/build-link-index.php --flush --tree=5   # only tree 5

The freshness flag is always reset, so the page reliably falls back to the live scan afterwards. The next full run is an initial build again (full re-scan cost). --flush combined with --rebuild is redundant — --rebuild already resets and rebuilds.

The overview and the index use one shared pre-filter (single source: XrefsService::linkPrefilterPatterns()):

  • NOTE (level >= 1) with an @XREF@ or a ](#@ link, with content on the same line. A NOTE value that is exactly one reference (1 NOTE @N5@) is a GEDCOM shared-note pointer — a structural reference, not a text link, and is not listed.
  • CONC/CONT/TEXT/_TODO with an @XREF@ (naked is a link) or a ](#@ link
  • an LE link may carry an optional id=@XREF@ parameter (at most one, the position among the parameters does not matter) — like the wt= parameters it is indexed as a link target
  • a shared note’s own level-0 text (0 @N1@ NOTE …)
  • matching is line-based (no cross-line matches), on the same lines the collector reads

The page shows which source it uses:

  • index active (green note): a full, complete index run finished less than 2 hours ago (default; XrefsService::INDEX_FRESH_SECONDS)
  • live scan (blue note): no fresh index — the page falls back to the live scan
  • limited mode (yellow note, SQLite/SQL Server): see the engine table above

Use p1-measure.php (read-only) on your instance to check how expensive the live scan is for your data and to calibrate the cron schedule.

1.2 - Renumber XREFs (with links)

Renumber XREFs with automatic link repair

The standard webtrees renumbering (Control panel Manage family trees → [Family tree] → Renumber XREFs) resolves cross-tree XREF collisions but does not update linkenhancer or classic links. This module provides an extended renumber that repairs all affected links in one step.

Access: Control panel → LinkEnhancer → Renumber XREFs (with links)

Workflow:

  1. Select the tree that has XREF collisions (duplicate XREFs with other trees).
  2. The page shows each conflicting XREF, its record type, and the number of inbound linkenhancer references from the link index.
  3. On submit, each conflicting XREF is:
    • renamed in the GEDCOM core tables (same as the standard renumber)
    • all wt=@XREF@ / id=@XREF@ links in notes and blocks are updated
    • the link index (le_link_index) and UID index (le_uid_index) are repaired inline or deferred to cron
  4. Each renumbered record is logged in the edit log (Admin → Changes log), e.g. Renumbered @I456@ → @I457@ (INDI, 3 core rows, 2 links).
  5. If the PHP time limit is reached, the run stops with partial progress — the remaining conflicts are renumbered on the next run.

Note: The standard webtrees renumber (without links) remains available and is unaffected. Use this module’s renumber when your notes or blocks contain linkenhancer links. See also: fisharebest/webtrees#5156

1.3 - Data Fixes

Data-fix handlers for link integrity issues

The module provides a set of data-fix handlers accessible from the webtrees data-fix interface (Control panel → Data fixes → LinkEnhancer). Each handler addresses a specific link integrity issue:

FixWhat it does
XREF ⇄ UID reference swapConverts linkenhancer link targets between XREF and UID form. Direction is selectable (XREF→UID or UID→XREF). Requires the UID index to be built.
Fix wt= type letterSets, repairs, or removes the legacy type letter in wt=<type>@REF@ links. Modes: set (add missing type), repair (fix wrong type), remove (strip type letter).
Report dangling link targetsReport-only: lists linkenhancer links whose target record no longer exists. No changes are made.
Report ambiguous UID targetsReport-only: lists links whose UID target resolves to more than one record. No changes are made.
Report duplicate UIDs in indexReport-only: lists UIDs assigned to multiple records in the UID index. No changes are made.

Each fix follows the webtrees data-fix workflow: select fix type → preview → apply. The data-fix page also shows the current status of the link and UID indexes, and whether index updates will be deferred to the cron job.

2 - Markdown

Markdown rendering in note records
Markdown logo

Markdown is a simple system of formatting, used on websites such as Wikipedia or GitHub. It uses unobtrusive punctuation characters to create headings and sub-headings, bold and italic text, lists, tables, etc.

Webtrees uses a CommonMark implementation and supports a subset of the markup. It is optionally supported in note records on a per-tree basis.

On the subject of markdown, see also:

Note: For the Markdown Image Support and the additional Markdown Extensions the MarkdownFactory class needs to be extended by this module. This replacement class also fixes fisharebest/webtrees#4841, so that a pre-formatted markdown block is not rendered as a single line anymore.

2.1 - Markdown Image Support

Embed images from GEDCOM media records or public folder in notes

note with links

This feature allows you to easily embed images from GEDCOM media records (which reside behind the media firewall) or from the public directory into notes. If restriction rules apply to the record, instead of the image, a message is displayed.

Syntax:

  • ![alternate text for gedcom object](#@id=@M1@)
  • ![alternate text for picture in public folder](#@public=webtrees.png "title")
  • ![picture with defined height, width and additional css class](#@id=@M1@&h=200&w=200&cname=float-right)

Technically the images are packed into a div container together with an image subtitle — which is also a link to the media data set for GEDCOM objects. The display can be customized as required using the standard CSS classes or per-image additional CSS classes (e.g. float-start or float-end from webtrees vendor.css).

2.2 - Markdown Editor

Visual markdown editor (TinyMDE) for note textareas

You can enable a visual markdown editor (mde) for note textareas. Under the hood the project TinyMDE - A tiny, dependency-free embeddable HTML/JavaScript Markdown editor is used.

Markdown editor

Besides syntax highlighting it ships with an icon bar for common format commands, a help popup and line numbering.

Markdown Help

Note: Unfortunately, the on-screen keyboard does NOT work as before with the previous text input field. The selected characters end up as an intermediate step in the small text field below the Markdown editor and then must be copied manually to the desired position.

2.3 - Markdown Extensions

Strikethrough, description lists, footnotes, highlight, TOC, smart punctuation

Warning: Please keep in mind when using these extensions, you are deviating from the smallest common markdown dialect set. If the library used to render markdown in HTML needs to be changed for any reason, there is a possibility that not all of the extended markup used here will be supported (see also #32). Since the markup is not intrusive, the impact should not be serious — of course, it’s a minor drawback.

The package League\CommonMarkdown used for processing Markdown texts offers several useful extensions that can be activated optionally:

You can activate all or a few selected of these extensions. As far as possible they are of course integrated with the markdown editor and you find additional information in the markdown help.

Footnotes

Footnotes can be placed near their references (there can be one or many references to one footnote) or everywhere you want. For the overview, it is advisable to place them at the bottom. When rendering, the footnote container resides at the end of the content.

Table of Contents

If your text is structured with headings, it is a good choice to activate the table of contents extension. The table of contents is generated automatically and included into the content above the very first heading or instead of a placeholder.

2.4 - Table cell height control

For longer markdown note texts it’s sometimes a pain to scroll through if you are interested in the next facts beneath them at the moment. This feature should come in handy here: By activating the checkbox in the top right corner of the title cell (first table column), the maximum height of the content cell with the long note is set to about 80% of the viewport height. This makes the content itself scrollable.

Where applicable, the function can be activated by default (or simply made available) and can also be deactivated independently.

Markdown table cell height control

In combination with the table of contents extension (position = dropdown), you can navigate through the text easily — assuming the text is structured with headings.

You can decide if the checkbox is always visible or hidden until you hover the fact title cell with the mouse cursor. If the text length of the content cell doesn’t exceed the defined maximum height, the checkbox can not be activated.

3 - German webtrees manual (WTHB)

Context-sensitive help links to the German webtrees manual
CompGen logo

A context sensitive help link to the German webtrees manual can be added to the small navigation menu. Context links are also available for components on pages provided by custom extensions (e.g. on the individuals page, the start page or items in the main menu) — these can be identified by the i symbols in a circle.

small menu with help link

WTHB subcontext help

To ensure that this function is also available in the admin backend, a modified version of views/layouts/administration.phtml must be registered. This module attempts to use the custom module vesta_common (if installed and enabled) or its own version of the view layout file. Alternatively, patch P002 could also be applied to the core code.

If the webtrees page is displayed in a language other than German, it is possible to open the webtrees manual URL via a translation service (Google Translate). This can be enabled or disabled as a site setting or delegated to the visitor to decide.

Note: Due to AI bot restrictions on the GenWiki, this feature is currently useless.

WTHB user setting

The settings dialogue can be accessed via the help submenu in the top menu. When this option is enabled, you will also find a link to a dialogue box that displays a full-text search in the German webtrees manual (using various search engines) and the table of contents. How the TOC itself is produced (GenWiki round trip, generation, Weblate) is documented on the WTHB TOC Language Pipeline page.

WTHB full-text search and toc

In addition, the submenu contains an overview of the help topics included in webtrees and customisable external help links (e.g. webtrees FAQ and forum, GitHub repo, etc.). The top menu item can be split into two links — one to directly open the context help article and the other to open the submenu.

WTHB submenu and split menu item

4 - Home Link

Site title as a configurable link with theme-aware CSS

The site title can be a link to the tree homepage or the user my page. The webmaster can also set a user-defined URL.

site title as link

Dynamic CSS styles can be applied to match the current theme, ensuring that the display contrasts well. Of course it is not limited to the home link use case - for details see Admin settings.

5 - UID Lookup

Navigate to a record by its persistent UID

A UID (_UID in GEDCOM 5.5.1, UID in GEDCOM 7.0) is a persistent identifier assigned to a record. The UID survives XREF renumbering and tree migrations, making it a stable reference point.

This feature allows navigating directly to a record by its UID. The URL pattern is:

  • /tree/{tree}/goto-uid/{uid} — scoped to one tree
  • /goto-uid/{uid} — global (searches all trees)

Behavior:

  • 0 hits → 404
  • 1 visible hit → redirect to the record
  • >1 visible hit → selection list (case-variant UIDs or the same UID in multiple records)

UID comparison is case-insensitive (GEDCOM specs do not define UID as case-sensitive). The stored value is verbatim; matching is done case-insensitively so that case-variant UIDs return a selection list rather than a possibly-wrong single redirect.

You can look up UIDs via the quick search input. For shorter UIDs that can be regarded as XREFs, the prefix uid: can be added to the search value.

Standard navigation (goto-id)

goto-id is the standard navigation target for every linkenhancer link — both untyped (wt=@REF@) and typed (wt=<type>@REF@) — and resolves a reference that is either a record XREF or a UID. The URL pattern is:

  • /tree/{tree}/goto-id/{id} — scoped to one tree (falls back to a global lookup when nothing is found in the tree, with a flash notice)
  • /goto-id/{id} — global (searches all trees)

Behavior (shared IdResolver): the id is tried length-aware and bidirectionally — a long id (≥ 18 chars) as a UID first, a short one as an XREF first — and falls back to the other when the first yields nothing, so a resolution is never lost. 0 hits → 404, 1 visible hit → redirect, >1 → selection list.

sequenceDiagram
    autonumber
    participant U as 👤 User (Browser)
    participant JS as LE JavaScript
    participant WT as webtrees Server
    participant IR as IdResolver
    participant DB as Database

    Note over U,JS: Click on an LE link
    U->>JS: Click on #@wt=@I234@
    JS->>JS: detect href "#@…"
    JS->>WT: GET /tree/5/goto-id/I234
    WT->>IR: resolve("I234", tree=5)

    alt ID short (< 18 chars) → try as XREF first
        IR->>DB: SELECT … WHERE xref = 'I234'
        DB-->>IR: Match?
    else ID long (>= 18 chars) → try as UID first
        IR->>DB: SELECT … FROM le_uid_index WHERE uid = '…'
        DB-->>IR: Match?
    end

    alt ✅ 1 match
        IR-->>WT: Record
        WT-->>U: Redirect to record page
    else 0 matches → Fallback
        IR->>IR: try the other direction (XREF ⇄ UID)
    else >1 matches
        WT-->>U: selection list
    end

    Note over IR,DB: Bidirectional — XREF ⇄ UID<br/>a lookup is never lost

A typed wt=<type>@REF@ link is a legacy form: the type letter is no longer used for routing (all links go through goto-id) and is only documentary — it still drives the diagram link for individuals and the type-mismatch hint in the cross-reference overview. New links should use the untyped wt=@REF@ form.

XREF route (goto-xref)

There is also the route /tree/{tree}/goto-xref/{xref} available, which allows you to navigate to the corresponding record in the tree, regardless of the record type.

Cross-reference overview: UID targets in other trees

In the admin Cross-Reference Overview a link whose UID target is not found in the record’s own tree is normally shown as “target not found”. When the UID feature is enabled, such a target is additionally resolved globally (across the other trees); if matches exist it is rendered as not unique (?) with the match count instead: up to 3 matches are listed as direct cross-tree links, more than 3 collapse into a single goto-id link (title = the UID). This behavior is hard-gated on the UID feature preference and never applies to targets that name an explicit tree.

Index

The UID index is stored in the le_uid_index table (one row per UID tag occurrence, distinguished by tag_path). It is maintained exclusively by build-uid-index.php (no live scan on request).

  • Initial build (first run, or --rebuild): full scan of all GEDCOM tables, extracting all UID tags via UidTagCollector.
  • Incremental runs: only records whose MD5 fingerprint changed (or were added/removed) are re-scanned.

Cron example (every 10 minutes, up to 5000 records per run):

*/10 * * * * cd /path/to/webtrees && php modules_v4/linkenhancer/cli/build-uid-index.php --limit=5000 >> /var/log/linkenhancer-uid-index.log 2>&1

The feature can be toggled on/off in the module admin page (“Activate Support” → [5] UID lookup). The current index status (row count, last build time) is shown there.

While the feature is switched off, index builds are skipped automatically (the cron run exits 0 without doing work). --force builds the index anyway (e.g. to prepare for re-enabling), and --flush works regardless of the switch.

Note: UID tags at any nesting level are indexed (e.g. 0 @I1@ INDI, 1 _UID, 1 FAMF, 2 _UID). The tag path is stored per row so duplicates are distinguishable in the selection list.

6 - Patches

Optional webtrees core patches

The patches can be applied additionally — the module also works without them!

The util subfolder contains minor patches for the webtrees core. These are diff-files that can be easily applied or removed using shell from within the installed module folder (necessary for auto-detecting webtrees sources):

Usage: util/wt-patch.sh [-R] [FILTER]
  -R                  undo Patch (patch -R)
  FILTER              '*' for all or specific (e.g. '01')

Don’t forget to reapply the patches after updating webtrees.

These are minor bug fixes or functional enhancements — usually in a single file — that are intended to bridge the gap until they are officially fixed/implemented in the webtrees core.

#DescriptionApplies to version
P001Backlink for level 1 shared notes #5181 — not necessary anymore except you would like to have an additional edit link. app/Fact.php2.2.1 - 2.2.6
P002Enable headContent/bodyContent for this module on admin backend in order to show the context help link #5214. resources/views/layouts/administration.phtml2.2.1 - 2.2.6
P003Record has multiple uid fields #4828. app/Services/GedcomEditService.php2.2.1