Qiraat Ayah Map

A numbering dataset. It answers one question — “the verse the Ḥafṣ muṣḥaf numbers 7:206, what number does it carry in the reading my user selected?” — and it carries no Qur’anic text at all.

Blocks used
Time
About 10 minutes

For the Arabic text of an ayah see Quran Text; for where it sits on a printed page, Quran SVG. If competing ayah counts are new to you, read Ayah-counting systems first; this page documents the data.

The six systems

SystemArabicTotal ayatAssociated qurrāʾ
First Madinan
madani-first
المدني الأول6,214abu-jafar
Last Madinan
madani-last
المدني الأخير6,214nafi
Makkan
makki
المكي6,219ibn-kathir
Basran
basri
البصري6,204abu-amr, yaqub
Damascene
dimashqi
الدمشقي6,226ibn-amir
Kufanreference
kufi
الكوفي6,236asim, hamza, kisai, khalaf

Read at build time from public/demo/qiraat.json, which scripts/sync-demo-data.mjs built from the Qiraat Ayah Map repository at a pinned commit.

A total does not identify a system

First Madinan and Last Madinan both total 6,214, and still place boundaries in different places — the differences cancel in the sum. So the totals column is for display, never for comparison. Details are on the concept page.

What ships

Everything is JSON, keyed by surah number then ayah number, both as strings. The Kufan system — the numbering of the Ḥafṣ muṣḥaf most of the world prints — is the hub: every mapping goes to it or from it, never directly between two non-Kufan systems.

FileWhat it holds
mappings/by-counting-system/kufi-to-<system>.jsonKufan ayah number → its number in that system
mappings/by-counting-system/<system>-to-kufi.jsonthe reverse direction
mappings/by-rawi/hafs-to-<rawi>.json, <rawi>-to-hafs.jsonthe same two tables, named by transmitter rather than by counting system
surah-counts/<system>.jsonayat per surah, and the total, in that system
rawis/<rawi>.jsonwhich counting system a transmitter follows, and which mapping file to load

Five non-Kufan systems means five forward files and five reverse files. At the commit this page was written against there are twenty transmitter files; eight of those transmitters are Kufan, so their numbering already is Ḥafṣ numbering and they carry no mapping file at all.

How you obtain these files today

The mapping, count and transmitter files are generated artefacts written to dist/, which is in the repository’s .gitignore — so they are not in the git tree, not attached to a release, and not on npm. To get them you clone the repository and run its generate script once, then copy dist/ into your project. The source layer under data/ is committed and directly fetchable, and so are dist/site-data.json and dist/mushaf/, which were committed explicitly when the project’s standalone website was retired — those are what this website reads. The full list is in source files.

Translate a reference

Working code first. load is whatever your project uses to read JSON — fetch, a bundler import, fs.readFile; the shape below is what matters.

const forward = await load("dist/mappings/by-counting-system/kufi-to-madani-last.json");
const reverse = await load("dist/mappings/by-counting-system/madani-last-to-kufi.json");

/** A Kufan (Ḥafṣ) ayah number → the ayah number(s) it occupies in the target system. */
function toSystem(mapping, surah, ayah) {
  const entry = mapping.surahs[String(surah)].ayahs[String(ayah)];
  return entry.status === "split" ? entry.splits_into : [entry.target_ayah];
}

/** An ayah number in some system → the Kufan ayah number(s) that cover it. */
function toKufan(mapping, surah, ayah) {
  const entry = mapping.surahs[String(surah)].ayahs[String(ayah)];
  return entry.status === "covers_multiple" ? entry.hafs_ayahs : [entry.hafs_ayah];
}

toSystem(forward, 1, 1);   // [ 1 ]        — merged with the next ayah
toSystem(forward, 1, 2);   // [ 1 ]        — the same target number as 1:1
toSystem(forward, 1, 7);   // [ 6, 7 ]     — one Kufan ayah becomes two
toSystem(forward, 2, 255); // [ 253, 254 ]

toKufan(reverse, 1, 1);    // [ 1, 2 ]

Both functions return an array: a boundary difference can make one ayah correspond to two, and returning a single number is the bug this dataset exists to prevent.

Forward files: read status, not just the number

Entries in a kufi-to-<system>.json file take one of three shapes.

statusMeaningExtra field
mappedone Kufan ayah, one target ayah
mergedthis ayah joins with the following one in the target systemmerges_with_next: true
splitthis ayah becomes two or more in the target systemsplits_into: number[]

Real entries from kufi-to-madani-last.json:

"1": { "target_ayah": 1, "status": "merged", "merges_with_next": true },
"2": { "target_ayah": 1, "status": "mapped" },
"7": { "target_ayah": 6, "status": "split", "splits_into": [6, 7] }
target_ayah alone silently loses ayat

Ḥafṣ 1:1 and 1:2 both carry target_ayah: 1, because Last Madinan counts them as a single ayah. Code that reads target_ayah and ignores status produces a plausible number every time: it renders two different verses under one number, and drops the second half of every split.

merges_with_next is not a fourth status: it can also appear alongside status: "split", when an ayah is split and its last part runs into the next ayah. In kufi-to-madani-last.json, surah 11 ayah 82 is { "target_ayah": 81, "status": "split", "splits_into": [81, 82], "merges_with_next": true }. Test for the field, not for the status, if merging is what you care about.

Reverse files: covers_multiple

Going the other way there are two shapes: mapped, with a hafs_ayah; and covers_multiple, which adds hafs_ayahs — the full array — because the target system’s single ayah spans more than one Kufan ayah. hafs_ayah is then the first of them, and using it alone truncates.

Reverse then forward is not the identity

Kufan 1:7 splits into Last Madinan 1:6 and 1:7. The reverse file maps both of those back to Kufan ayah 7, with status: "mapped" in each case, so a round trip through Kufan numbering cannot tell you which half you started from. Convert Kufan → target for display, and keep the Kufan reference as the stored value; do not use the round trip to “normalise” a number.

Per-surah counts

surah-counts/<system>.json holds _counting_system, _total_ayahs, and surahs — surah number to ayah count.

const counts = await load("dist/surah-counts/basri.json");
counts._total_ayahs; // 6204
counts.surahs["2"];  // 287
A per-surah count cannot be derived from the totals

Basran totals 6,204 — thirty-two fewer ayat than Kufan’s 6,236 — and yet its al-Baqarah has 287 ayat against Kufan’s 286, one more. Differences run in both directions and cancel. There is no per-surah delta to compute from the totals, and no delta to add across a surah either: read the count, or map the reference.

Starting from a transmitter, not a system

If your UI offers readings rather than counting madhhabs, rawis/<rawi>.json is the join. It is small and complete:

{
  "_rawi": "warsh",
  "_qiraa": "nafi",
  "_counting_system": "madani-last",
  "_mushaf_id": 4,
  "_mapping_file": "mappings/by-counting-system/kufi-to-madani-last.json"
}

A null _mapping_file means the transmitter is Kufan and needs no translation — that is the case for Ḥafṣ himself, and for the seven other Kufan transmitters.

  1. Store references in Kufan numbering

    Pick one numbering for everything you persist — bookmarks, links, your own annotations — and let Kufan be it, because it is the hub every mapping file connects to. Translate on the way out to the screen only.

  2. Load the two files for the reading being displayed

    Read rawis/<rawi>.json to find _counting_system, then load kufi-to-<system>.json and <system>-to-kufi.json. If _mapping_file is null, skip both — the numbers are already right.

    const rawi = await load(`dist/rawis/${selected}.json`);
    const system = rawi._counting_system;              // e.g. "madani-last"
    const forward = rawi._mapping_file
      ? await load(`dist/${rawi._mapping_file}`)
      : null;
    
  3. Branch on status every time you convert

    Handle split and covers_multiple explicitly. A conversion helper that returns a bare number is where the bugs come from; return the array and let the caller decide how to render a two-number range.

The boundary layer underneath

data/book-boundary-primitives.json is the committed, hand-maintained source the mappings are derived from — and unlike dist/ it can be fetched from the repository directly. It records only the places where the traditions disagree, keyed by surah and by Kufan ayah number. Al-Fātiḥa in full:

"1": {
  "1": { "end": { "word": "الرحيم", "counted_by": ["makki", "kufi"] } },
  "7": { "internal": [{ "word": "عليهم", "counted_by": ["madani-first", "madani-last", "basri", "dimashqi"] }] }
}

Read that as: Makkan and Kufan end an ayah at the word al-Raḥīm; the other four instead place a boundary inside Kufan ayah 7, at the word ʿalayhim. Both groups reach seven ayat by different routes.

word is a bare orthographic word for human identification, not a join key — it is not the muṣḥaf’s spelling and it carries no word index. To locate the boundary in text you render, use the ayah numbers from the mapping files against Quran Text; do not string-match on this field.

Limits

One assignment is contested, and we have not settled it

This dataset assigns the qirāʾah of Abū ʿAmr — whose transmitters are al-Dūrī and al-Sūsī — to the Basran system, which totals 6,204. The printed al-Sūsī muṣḥaf that Quran Text measures numbers 6,218 ayat, which is not Basran. Join the two datasets on a transmitter and you will meet the contradiction. It is unresolved; see Ayah-counting systems for what to do meanwhile.

Try it live

Pick a reference and watch it resolve to a different number in each of the six systems, from this dataset.

How this data is made

The source layer is the boundary primitives shown above: each disputed point recorded once with the systems that count it, backed by an evidence entry citing the classical works it comes from. Every mapping file, per-surah count and total on this page is derived from that layer. Rebuilding from the primitives reproduces all six published totals — 6,236, 6,226, 6,219, 6,214, 6,214 and 6,204 — and the repository’s validation suite checks the forward and reverse mappings against each other and against the counts, ayah by ayah. What a position claims, who transmitted it, and how well attested it is are set out in method and evidence; the file shapes are in source files.