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.
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
| System | Arabic | Total ayat | Associated qurrāʾ |
|---|---|---|---|
| First Madinan madani-first | المدني الأول | 6,214 | abu-jafar |
| Last Madinan madani-last | المدني الأخير | 6,214 | nafi |
| Makkan makki | المكي | 6,219 | ibn-kathir |
| Basran basri | البصري | 6,204 | abu-amr, yaqub |
| Damascene dimashqi | الدمشقي | 6,226 | ibn-amir |
| Kufanreference kufi | الكوفي | 6,236 | asim, 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.
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.
| File | What it holds |
|---|---|
| mappings/by-counting-system/kufi-to-<system>.json | Kufan ayah number → its number in that system |
| mappings/by-counting-system/<system>-to-kufi.json | the reverse direction |
| mappings/by-rawi/hafs-to-<rawi>.json, <rawi>-to-hafs.json | the same two tables, named by transmitter rather than by counting system |
| surah-counts/<system>.json | ayat per surah, and the total, in that system |
| rawis/<rawi>.json | which 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.
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.
| status | Meaning | Extra field |
|---|---|---|
| mapped | one Kufan ayah, one target ayah | — |
| merged | this ayah joins with the following one in the target system | merges_with_next: true |
| split | this ayah becomes two or more in the target system | splits_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] }
Ḥ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.
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
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.
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.
Load the two files for the reading being displayed
Read
rawis/<rawi>.jsonto find_counting_system, then loadkufi-to-<system>.jsonand<system>-to-kufi.json. If_mapping_fileisnull, 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;Branch on status every time you convert
Handle
splitandcovers_multipleexplicitly. 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
- No Qur’anic text. Nothing here is renderable content. Pair it with a text or page dataset.
- Numbering only, not recitation. Two readings can share a counting system and still differ in letters and pronunciation. Sharing a system means their ayah numbers agree, nothing more.
- Kufan is the hub. There is no
basri-to-makki.json. Convert through Kufan in two steps, and handlestatusat both of them. - The dataset is versioned as
0.1.0— that is the_versioncarried by the mapping and count files, and it is the package version, not a per-file one. There is no release tag to pin to. Pin the commit SHA instead, as this site does.
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.
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.