Riwayat
The Qur’an reaches us through several transmitted readings. They are all the same text, and they are not the same data. This page is about the second half of that sentence: what actually changes in the files when a user picks a different reading, so you can decide what to cache, what to key, and what to re-resolve.
The two-level hierarchy matters only because our identifiers sit on the lower level. hafs, warsh, qalun,
duri, susi, shubah and bazzi are transmitters' names, and each one is a selector for a complete printed
text.
The seven we ship
| Key | Riwayah | Counting system | Ayat in this muṣḥaf | System total |
|---|---|---|---|---|
| hafs | Ḥafṣ ʿan ʿĀṣim | Kufi | 6,236 | 6,236 |
| shubah | Shuʿbah ʿan ʿĀṣim | Kufi | 6,236 | 6,236 |
| warsh | Warsh ʿan Nāfiʿ | Last Madani | 6,214 | 6,214 |
| qalun | Qālūn ʿan Nāfiʿ | Last Madani | 6,214 | 6,214 |
| duri | al-Dūrī ʿan Abī ʿAmr | First Madani | 6,217 | 6,214differs |
| susi | al-Sūsī ʿan Abī ʿAmr | First Madani | 6,218 | 6,214differs |
| bazzi | al-Bazzī ʿan Ibn KathīrBeta | Makki | 6,220 | 6,219differs |
Muṣḥaf counts come from quran-text’s own files; system totals from qiraat-ayah-map. Both are read at build time. Where they differ, both numbers are correct about different questions — see below.
Two of the transmitters share a reciter in three places — Ḥafṣ and Shuʿbah both transmit ʿĀṣim, Warsh and Qālūn both transmit Nāfiʿ, al-Dūrī and al-Sūsī both transmit Abū ʿAmr — and that pairing does not make their data interchangeable. At 6:124 the Ḥafṣ text reads رِسَالَتَهُۥ where Shuʿbah reads رِسَٰلَٰتِهِۦ; al-Dūrī and al-Sūsī differ in their printed ayah count. Sharing a reciter tells you nothing you can use in code.
What changes when the riwayah changes
| Riwayah | Words printed | Ayat printed | Basmalah numbered | Ayat in surah 36 | First word of 1:1 |
|---|---|---|---|---|---|
| Ḥafṣ hafs | 77,432 | 6,236 | yes | 83 | بِسۡمِ |
| Shuʿbah shubah | 77,432 | 6,236 | yes | 83 | بِسۡمِ |
| Warsh warsh | 77,431 | 6,214 | no | 82 | ࡴ۬لْحَمْدُ |
| Qālūn qalun | 77,431 | 6,214 | no | 82 | ࡴ۬لْحَمْدُ |
| Dūrī duri | 77,431 | 6,217 | no | 82 | اِ۬لۡحَمۡدُ |
| Sūsī susi | 77,431 | 6,218 | no | 82 | ࡵ۬لۡحَمۡدُ |
| Bazzī bazzi | 77,432 | 6,220 | yes | 82 | بِسۡمِ |
Read at build time from public/demo/fatiha.json and public/demo/lookup.json, which scripts/sync-demo-data.mjs built from each muṣḥaf file in quran-text at the commit pinned in public/demo/SOURCES.json. The last column is the first word of what each edition numbers ayah 1 of surah 1: where the basmalah is not numbered, ayah 1 begins after it.
Read that table left to right and you have the list of things a riwayah switch invalidates:
| Changes with the riwayah | Evidence in the table | Consequence |
|---|---|---|
| The number of words | 77,432 against 77,431 | any array index into words belongs to one edition |
| The number of ayat | five different totals across seven editions | any for (a = 1; a <= N; a++) belongs to one edition |
| Whether the basmalah is ayah 1 | yes for three, no for four | 1:1 is not the same words in every reading |
| Where ayat end inside a surah | surah 36 has 83 ayat in Ḥafṣ and 82 in the others | Ḥafṣ numbers the opening letters alone as 36:1; Warsh does not, so 36:2 is already a different verse — see ayah counting |
| The counting system | four systems across seven editions | translate references, never add an offset |
The letters change
The same word, at the same position, in all seven:
| Riwayah | As printed | Code points |
|---|---|---|
| Ḥafṣ hafs | مَٰلِكِ | U+0645 U+064E U+0670 U+0644 U+0650 U+0643 U+0650 |
| Shuʿbah shubah | مَٰلِكِ | U+0645 U+064E U+0670 U+0644 U+0650 U+0643 U+0650 |
| Warsh warsh | مَلِكِ | U+0645 U+064E U+0644 U+0650 U+0643 U+0650 |
| Qālūn qalun | مَلِكِ | U+0645 U+064E U+0644 U+0650 U+0643 U+0650 |
| Dūrī duri | مَلِكِ | U+0645 U+064E U+0644 U+0650 U+0643 U+0650 |
| Sūsī susi | مَّلِكِ | U+0645 U+064E U+0651 U+0644 U+0650 U+0643 U+0650 |
| Bazzī bazzi | مَلِكِ | U+0645 U+064E U+0644 U+0650 U+0643 U+0650 |
Word position 10 of surah 1 in each edition’s own words array, from public/demo/fatiha.json. It is the same word in every reading — the first word of the ayah Ḥafṣ numbers 1:4 — and the same shared word number in Quran Text.
Three visibly different spellings. And where two editions do look the same, the code points still need not
match: the first word of the basmalah is بِسۡمِ in the Ḥafṣ text,
with its sukūn as U+06E1, and بِسْمِ in Warsh, with U+0652.
So even "the same word looks the same" does not give you string equality across editions. The only thing that survives is the shared word number — see
Word identity.
Across the whole text, this is how often the seven disagree at the level of letters, spelling of the ā, word division or presence:
status | Words | What it means |
|---|---|---|
| alif_variant | 198 | one skeleton once every ā is spelled out — the ā is on the line in one hand and above it in another |
| rasm_variant | 60 | the editions disagree about the letters on the line |
| word_boundary | 16 | a source prints the word joined to its neighbour, or a muṣḥaf really writes it joined |
| partial | 3 | the word is absent from at least one edition |
| Total | 277 | words where at least one edition differs at this level |
Read at build time from public/demo/differences.json, the demo’s copy of data/differences.json in quran-text. The descriptions paraphrase the status vocabulary in that repository’s docs/files.md; the file itself carries only the key.
The vowelling differs far more often than that — the differences file leaves out words that differ only in their marks — so treat these counts as a floor, not as "277 places where the readings differ".
It is tempting to model the reading as a toggle beside dark mode: keep one text in memory and switch the font.
Nothing throws when you do this. A cache keyed on 2:255, a search index built over one edition, or an
annotation offset computed against one text is served to a reader of another.
Choose the riwayah when you load, put its key in every cache key, and re-resolve every stored reference through
the mapping when it changes — a riwayah is an entity, not a column on one (Engineering, rule 1.2).
The font changes
Each edition is typeset in its own publisher font, and it is not decorative. Quran Text names the family in every
muṣḥaf file — KFGQPC HAFS Uthmanic Script, KFGQPC Warsh Uthmanic Script and so on — and the demo on this site
loads seven woff2 files, one per edition, because three of the editions use code points almost no general font
draws. The Quran Text reference has the details; the short version is
that "the text renders as boxes" is a font problem, not missing data.
Which blocks cover which riwayat
Coverage is uneven: splitting a muṣḥaf into words is hard, and annotating tajwīd is scholarly work per reading. Read this before promising a feature in every reading.
| Block | Riwayat covered | Where it says so |
|---|---|---|
| Quran Text | all seven | data/catalog.json |
| Qiraat Ayah Map | all — it maps counting systems, not texts | rawis/ |
| Quran SVG | Ḥafṣ, Warsh, Qālūn, al-Dūrī, Shuʿbah | the mushafs/ folders |
| Quran SVG Elements | Ḥafṣ only (hafs-kfgqpc) | VERSION.json |
| Quran Engine | the split muṣḥafs — Ḥafṣ today | the release's page data |
| Quran Tajweed | Ḥafṣ only; another riwayah is an error, not a setting | riwayah: "hafs-an-asim" in the annotations |
| Quran Assets | ornaments for eight printed muṣḥafs | the index's mushafs[].key |
So a reader of Warsh can have text, page artwork and reference translation, but not word-level tapping and not tajwīd colouring. Design the feature matrix from this table, not from the Ḥafṣ experience.
What to do
Make the riwayah part of every key
Cache entries, bookmarks, search indexes, audio timings: all of them are per edition.
type Riwayah = "hafs" | "shubah" | "warsh" | "qalun" | "duri" | "susi" | "bazzi"; // wrong — collides the moment a second reading is loaded cache.set(`ayah:${surah}:${ayah}`, text); // right cache.set(`ayah:${riwayah}:${surah}:${ayah}`, text);Read counts from the loaded edition
Every muṣḥaf file carries its own
counting.ayah_countandmushaf.word_count. Use them; never a literal.Join across editions on the word number, not on text
Quran Text gives every word one integer shared by all seven editions. That is the only safe way to say "this word" in more than one reading at once.
Translate references when the reading changes
A stored
36:2in Ḥafṣ is a different verse in Warsh. Use the mapping rather than arithmetic; the offset is not constant within a surah. Map ayah references between counts is the guide.
The same ayah in all seven editions, from the real dataset, with each edition’s own spelling and boundaries.
How this data is made
Each of the seven editions in Quran Text is extracted unedited from a digital package published by the King Fahd Glorious Qurʾān Printing Complex, the Madinah muṣḥaf in that reading, and the file names the package and its SHA-256. The cross-edition comparison, the shared word numbering and the measurement of which counting system each edition follows are derived by the build. The seven are a complete comparison of the material the publisher provides, not of the ten canonical qirāʾāt: three reciters have no published edition here.