Quran metadata for JavaScript and TypeScript: surahs, ayahs, juz, hizb, rub' al-hizb, thumun al-hizb, mushaf pages, rukus, manzils and sajdas for eight riwayas, with conversion between their ayah numberings.
| Project | |
| Package | |
| Build & tests | |
| Usage | |
| Scores | |
| Size |
Live demo · Surah table · Playground · API reference · Changelog
"2:255" and "1:1-7" to tuples and back.customizeRiwaya.quran-meta/i18n, bundled one language at a time.Surah is 1 | 2 | ... | 114, Juz is 1 ... 30, with type guards and assertion functions.npm i quran-meta # or: pnpm add quran-meta / yarn add quran-meta / bun add quran-meta
The package is ESM-only and needs Node 22 or newer. On Node 22.12+ require("quran-meta") also works, since Node can require() ES modules.
For CommonJS on older Node versions, stay on v6: npm i quran-meta@6 installs 6.1.1, which has a CommonJS build of its main entry, so const { quran, createWarsh } = require("quran-meta") works there. Its riwaya subpaths (quran-meta/hafs, ...) are ESM-only, so use createWarsh() and friends from the main entry instead.
In the browser without a bundler:
<!-- ES module -->
<script type="module">
import { quran } from "https://cdn.jsdelivr.net/npm/quran-meta/+esm"
console.log(quran.getSurahMeta(2).ayahCount) // 286
</script>
<!-- Classic script, exposes window.quranMeta -->
<script src="https://cdn.jsdelivr.net/npm/quran-meta"></script>
<script>
console.log(quranMeta.meta.numAyahs) // 6236
</script>
<!-- Optional: surah names in every language, exposes window.quranMetaI18n -->
<script src="https://cdn.jsdelivr.net/npm/quran-meta/dist/quran-meta-i18n.iife.js"></script>
<script>
console.log(quranMetaI18n.getSurahName(2, quranMetaI18n.surahNamesFr)) // ["Al Baqarah", "La vache"]
</script>
Import from the entry point of the riwaya you need. Functions come with that riwaya's data already bound, and only that riwaya ends up in your bundle.
import { findJuz, findPage, getAyahMeta, getSurahMeta, meta, nextAyah, quran } from "quran-meta/hafs"
meta.numAyahs // 6236
findJuz(2, 142) // 2
findPage(2, 255) // 42
getSurahMeta(2).ayahCount // 286
getAyahMeta(262) // { surah: 2, ayah: 255, juz: 3, page: 42, hizbId: 5, rubAlHizbId: 17, ruku: 35, isSajdahAyah: false, ... }
nextAyah(1, 7) // [2, 1]
nextAyah(114, 6) // [1, 1], wraps around by default
nextAyah(114, 6, { wrap: false }) // undefined
// The same API as a class
quran.getSurahMeta(2).name // "البَقَرَة"
The entry points are quran-meta/hafs, /shuba, /warsh, /qalun, /douri, /sousi, /bazzi and /qunbul. Each exports meta, the raw lists (PageList, JuzList, ...), all functions, create<Riwaya>() and a ready-made quran instance.
The root quran-meta entry has the riwaya-agnostic functions, which take the riwaya data as their last argument. Use it when you work with several riwayas at once:
import { QuranRiwaya, findJuz, getListsOfRiwaya } from "quran-meta"
const warsh = getListsOfRiwaya("Warsh")
findJuz(2, 142, warsh)
const qalun = QuranRiwaya.create(getListsOfRiwaya("Qalun"))
qalun.findThumunAlHizb(2, 1) // thumun al-hizb is Qalun-only, other riwayas get a type error
| Riwaya | Narrated from (qari) | Entry point | Ayahs | Sajdas |
|---|---|---|---|---|
| Hafs | 'Asim | quran-meta/hafs |
6236 | 15 |
| Shuba | 'Asim | quran-meta/shuba |
6236 | 15 |
| Warsh | Nafi' | quran-meta/warsh |
6214 | 14 |
| Qalun | Nafi' | quran-meta/qalun |
6214 | 12 |
| Douri | Abu 'Amr | quran-meta/douri |
6217 | 15 |
| Sousi | Abu 'Amr | quran-meta/sousi |
6217 | 15 |
| Bazzi | Ibn Kathir | quran-meta/bazzi |
6221 | 15 |
| Qunbul | Ibn Kathir | quran-meta/qunbul |
6221 | 15 |
All riwayas have surah, juz, hizb, rub' al-hizb, page, ruku, manzil and sajda data. Qalun also has thumun al-hizb (480 eighths). See VisualQuran for background on the riwayas.
Riwayas follow different ayah counting traditions, so the same text can have different numbers. In Hafs الم is ayah 2:1 on its own; in Warsh it opens 2:1 together with what Hafs numbers 2:2. Ayat al-Kursi (Hafs 2:255) is two ayahs in Warsh.
import { convertAyah, convertAyahSpan } from "quran-meta"
convertAyah(2, 255, "Hafs", "Warsh") // [2, 253], the Warsh ayah where Hafs 2:255 starts
convertAyahSpan(2, 255, "Hafs", "Warsh") // [2, [253, 254]], every Warsh ayah sharing its text
convertAyah(1, 1, "Warsh", "Hafs") // [1, 2], Warsh does not count the basmala
// From a riwaya entry point the source riwaya is implied
import { convertAyahTo } from "quran-meta/warsh"
convertAyahTo(2, 253, "Hafs") // [2, 255]
The conversion table is generated by aligning the text of each riwaya with the Hafs text (KFQC data, quranpedia for Bazzi and Qunbul) and is checked against the ayah counts of every riwaya. Riwayas from the same qari (Warsh/Qalun, Douri/Sousi, Bazzi/Qunbul, Hafs/Shuba) come out identical, which is a useful cross-check since Warsh and Qalun come from separate source files.
import { ayahsInPage, ayahsInPart, getPartRange } from "quran-meta/hafs"
for (const [surah, ayah] of ayahsInPage(604)) {
// 112:1 ... 114:6
}
const juzAmma = [...ayahsInPart("juz", 30)] // 564 [surah, ayah] pairs
getPartRange("ruku", 1) // [1, 7], first and last ayah id
Part types are "surah", "juz", "rubAlHizb", "thumunAlHizb", "page", "manzil" and "ruku".
import { ayahStringSplitter, formatSurahAyah, formatAyahId, surahStringParser } from "quran-meta/hafs"
ayahStringSplitter("2:255") // [2, 255]
ayahStringSplitter("18:1-10") // [18, [1, 10]]
ayahStringSplitter("2:280-290") // throws RangeError, Al-Baqarah has 286 ayahs
formatSurahAyah([18, [1, 10]]) // "18:1-10"
formatAyahId(262) // "2:255"
surahStringParser("36") // 36
The main entry has English and Arabic. The other languages are in quran-meta/i18n, one table per language, and your bundle gets only the tables you import:
import { getSurahName, surahNamesAr } from "quran-meta"
import { surahNamesFr } from "quran-meta/i18n"
getSurahName(2) // ["Al-Baqara", "The Cow"]
getSurahName(2, surahNamesAr) // ["البَقَرَة", "البقرة"], with and without tashkeel
getSurahName(1, surahNamesFr) // ["Al Fâtiha", "L'ouverture"]
Each entry is [transliteration, translation]. Arabic holds the name with and without tashkeel instead, and Malayalam the Latin transliteration and the name in Malayalam script.
To pick the language at runtime, load it on demand. Each language is its own chunk, and only the one you ask for is downloaded:
import { getSurahName } from "quran-meta"
import { getSurahNamesAsync, languages } from "quran-meta/i18n/async"
languages // ["en", "ar", "az", "ru", "tr", "uz", "kk", "fr", "lt", "tg", "ky", "bs", "bn", "es", "id", "it", "ml", "ms", "nl", "sv", "ur", "zh"]
getSurahName(2, await getSurahNamesAsync("ru")) // ["Аль-Бакара", "Корова"]
getSurahNames(lang) and surahNames from quran-meta/i18n also take the language at runtime, but they bundle all 22.
Every number type is a union of its valid values, so Surah is 1 | 2 | ... | 114. Type guards narrow unknown input and assertion functions throw on bad input:
import { checkValidSurahAyah, getAyahCountInSurah, isValidSurah } from "quran-meta/hafs"
function handle(input: unknown) {
if (isValidSurah(input)) {
getAyahCountInSurah(input) // input is Surah here
}
}
const ayah: unknown = 255
checkValidSurahAyah(2, ayah) // throws RangeError unless 2:ayah exists; afterwards ayah is AyahNo
customizeRiwaya starts from a built-in riwaya, replaces some lists and recalculates the counts in meta. Use it for a different mushaf page layout or your own juz or ruku divisions:
import { QuranRiwaya, customizeRiwaya, getListsOfRiwaya } from "quran-meta"
// Boundary lists hold the first ayah id of each part: [0, part1, part2, ..., numAyahs + 1]
const indoPak = customizeRiwaya(getListsOfRiwaya("Hafs"), { PageList: myIndoPakPageList })
const quran = QuranRiwaya.create(indoPak)
quran.meta.numPages // the length of your page list
quran.findPage(2, 255)
The result is checked with validateRiwayaData, which you can also call yourself; it reports lists that are out of order, do not start at ayah 1 or do not end after the last ayah.
The full reference is at quran-center.github.io/quran-meta/docs, grouped by topic: class API, surah and ayah, navigation, juz, page, hizb, manzil and ruku, ranges and iteration, parsing and formatting, validation, riwaya conversion, riwaya data and surah names.
lib_cjs/ and lib_es/ are gone. On Node 22.12+ require("quran-meta") still works; for CommonJS on older Node versions, stay on quran-meta@6 (see Install). The IIFE bundle is now dist/quran-meta.iife.js and is minified; the separate .min.js builds are gone (jsDelivr serves minified files on request).ayah is now a required argument of findJuz, findJuzMetaBySurah, findManzil, findPage, findRubAlHizb and findThumunAlHizb (it used to default to 1 before the lists argument). Riwaya entry points and the class keep the default.QalunLists instead of riwayaLists, like every other entry.QuranRiwaya thumun al-hizb methods exist only on Qalun instances at the type level and no longer claim to return null.ayahStringSplitter now checks both ends of a range against the surah ("2:280-290" used to pass).languages (and the Lang type) gained Arabic and ten more languages, so code that builds a SurahNamesI18n object needs entries for them.findPagebyAyahId is now findPageByAyahId, and SurahJuzMeta.leftjuz is now leftJuz.surahNames, getSurahNames, languages and the Lang and SurahNamesI18n types moved from quran-meta to quran-meta/i18n (languages is also in quran-meta/i18n/async), so the main entry no longer carries every language. For a classic <script>, they are in dist/quran-meta-i18n.iife.js, which exposes window.quranMetaI18n.pnpm verify checks the lists against these sources, and pnpm verify:lists cross-checks them against the quranpedia dumps they were generated from:
qcloud-meta.json: AlQuran Cloud API metadatatanzil-data.js: Tanzil.net metadataquran-api.json: Quran API metadatahafsData_v2-0.json, hafs_smart_v8.json: KFGQPC Hafs font dataSousiData_v2-0.json: KFGQPC Sousi font dataDouriData_v2-0.json: KFGQPC Douri font dataQalounData_v2-1.json: KFGQPC Qaloun font datashubaData_v2-0.json: KFGQPC Shuba font datawarshData_v2-1.json: KFGQPC Warsh font datadata/quranpedia/*.json: quranpedia.net dumps for all eight riwayas, the source of the generated lists in src/lists/KFQC publishes no data for Bazzi and Qunbul, so their lists are checked against each other (both are from Ibn Kathir and must match). Known differences with the KFQC data (a few juz boundaries in Douri and Sousi, some page breaks) are listed in the check scripts under examples/data-check/.
Surah names in Bengali, Chinese, Dutch, Indonesian, Italian, Malay, Malayalam, Spanish, Swedish and Urdu come from the Quran.com API and are generated by examples/data-check/generate-surah-names.ts, which also compares them with the API when run with --check. Quran.com has no transliterations in these languages, so their first element is the Latin transliteration.
pnpm install
pnpm check # format, lint, types, generated files and tests, the same as CI
pnpm dev # tests in watch mode
pnpm build # dist/ via tsdown
pnpm generate # regenerate the riwaya entry points and the conversion table
pnpm docs:site # build the landing page and API reference into _site/
pnpm verify # check the data against external sources
Riwaya entry points (src/hafs.ts, ...) are generated by scripts/generate-entries.ts and the conversion table by examples/data-check/generate-ayah-map.ts; CI fails if either is out of date. A pre-commit hook (installed by pnpm install) runs formatting, lint, type and test checks.

MIT