@quranjs/api
v3.10.0
Published
Official library for fetching authentic, scholarly verified Quran data from Quran.com.
Readme
@quranjs/api
A JavaScript/TypeScript library for fetching authentic, scholarly verified Quran data from the Quran.com API.
Unlike other sources, this SDK connects you directly to the Quran Foundation—ensuring a trusted, highly scrutinized source of reliable content, including properly licensed translations, tafsir, and supplementary materials.
Works in both server and browser environments through separate runtime entrypoints:
@quranjs/api/server@quranjs/api/public
Built by the Quran Foundation — the team behind Quran.com
Installation
# npm
npm install @quranjs/api
# yarn
yarn add @quranjs/api
# pnpm
pnpm add @quranjs/apiQuick Start
import { SearchMode } from "@quranjs/api";
import { createServerClient } from "@quranjs/api/server";
const client = createServerClient({
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
});
const chapters = await client.content.v4.chapters.list();
const results = await client.search.v1.query({
query: "mercy",
mode: SearchMode.Quick,
});Analytics Events
Analytics submission uses the analytics.events.write scope and is available
only from the server entrypoint. The SDK obtains and caches the required
client-credentials token. Keep CLIENT_SECRET in server-side environment
variables.
const result = await client.analytics.v1.events.submit({
events: [
{
eventId: crypto.randomUUID(),
name: "quran.reader.verse_viewed",
version: 1,
occurredAt: new Date(),
userId: "QURAN_FOUNDATION_USER_ID",
sessionId: "session-123",
properties: { verseKey: "2:255", surface: "reader" },
},
{
eventId: crypto.randomUUID(),
name: "quran.app.started",
version: 1,
occurredAt: new Date(),
anonymousId: "anonymous-123",
},
],
});A successful response accepts the complete batch. Retry a failed batch with the same event IDs so downstream processing can identify duplicates.
For browser or mobile apps, use @quranjs/api/public. Public usage docs live in the API docs portal.
App State
App State stores app-owned JSON documents for signed-in users. It is available
from both runtime entrypoints under client.auth.v1.appState. Read the enabled
data groups before writing, use a fresh high-entropy idempotency key for each
logical mutation, and store quoted ETags unchanged.
const config = await client.auth.v1.appState.getConfiguration();
const created = await client.auth.v1.appState.putDocument(
"settings",
"theme",
{ value: { mode: "dark" }, schemaVersion: 1 },
{ idempotencyKey: crypto.randomUUID(), ifNoneMatch: "*" },
);
const current = await client.auth.v1.appState.getDocument("settings", "theme");
await client.auth.v1.appState.putDocument(
"settings",
"theme",
{ value: { mode: "light" }, schemaVersion: 1 },
{ idempotencyKey: crypto.randomUUID(), ifMatch: current.etag! },
);For offline startup, page through bootstrap() until hasMore is false and
then persist nextSyncToken. Apply each getChanges() page and its next token
atomically. On HTTP 410, preserve pending writes, bootstrap and drain changes,
replay pending writes, and then pull again. An unchanged replay request retains
its idempotency key; a conflict rebase rotates it with the changed fingerprint.
For transactional offline reconciliation, provide an account-scoped durable
AppStateStore. Its transaction(accountId, reducer) implementation must
initialize missing accounts, run the reducer synchronously, and atomically
commit the complete draft only when the reducer returns successfully. Reducers
must not perform network I/O. The reconciler stages bootstrap pages separately,
applies change pages with their tokens atomically, replays immutable local
replacements, and rejects responses from an account that is no longer active.
import { createAppStateReconciler } from "@quranjs/api/public";
const appState = createAppStateReconciler({
accountId: signedInAccountId, // Explicit identity; never derive it from a token.
store: durableAppStateStore,
transport: client.auth.v1.appState,
});
await appState.putDocument("settings", "theme", {
schemaVersion: 1,
value: { mode: "dark" },
});
await appState.reconcile();
const state = await appState.getState();
const theme = state.visible["settings/theme"];
await appState.switchAccount(
nextSignedInAccountId,
nextAccountClient.auth.v1.appState,
);Account switching replaces the local account boundary and transport atomically. Create a separate client/transport whose immutable session belongs to the target account; do not pass a facade that reads a mutable cross-account session at request time. An in-flight request retains the transport captured for its original account, and its late result cannot commit after the generation changes.
putDocument() and deleteDocument() only queue local mutations. Call
reconcile() to pull, replay the captured pending set, and pull again. Calls to
reconcile() are serialized, while local queue writes remain available. On a
strict 412 conflict, the complete replacement is rebased onto the refreshed
ETag with a new idempotency key. createAppStateMemoryStore() is available for
tests and short-lived sessions; it is not durable across process restarts.
Existing QuranClient imports from @quranjs/api remain supported for backwards compatibility:
import { QuranClient } from "@quranjs/api";
const client = new QuranClient({
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
});
const chapters = await client.chapters.findAll();For new apps, prefer the runtime-specific @quranjs/api/server and @quranjs/api/public entrypoints.
Documentation
For complete documentation, guides, and API reference, visit:
Features
- 🚀 Full TypeScript support
- 🌐 Works in Node.js and browsers
- ✅ Scholarly verified data
- 📖 Access chapters, verses, juzs, and more
- 🔍 Full-text search
- 🎧 Audio recitations
- 🌍 Multiple verified translations and languages
Content Sync
Bootstrap an approved public Mushaf, download its snapshot for offline use, and then poll the same resource filter for incremental changes:
import type { MushafSnapshotRecord } from "@quranjs/api";
const changes = await client.resources.sync({
bootstrap: true,
resources: "mushafs:1",
});
const snapshot = await client.resources.findSnapshot<MushafSnapshotRecord>(
"mushafs",
1,
);Mushaf snapshots include layout metadata, pages, publicly distributable font
assets, and words. Store the final nextSyncToken and use it with the same
resources filter on subsequent sync calls.
Once published, the singleton quran_core:1 provides canonical Uthmani verse
text, Surah metadata, and Juz/Hizb/Rub-el-Hizb boundaries without duplicating
them in every Mushaf snapshot. Mushaf-specific pages and glyphs remain in
mushafs:<id>. Publication is pending content/licensing approval.
import type { QuranCoreSnapshotRecord } from "@quranjs/api";
await client.resources.sync({
bootstrap: true,
resources: "mushafs:1;quran_core:1",
});
const core = await client.resources.findSnapshot<QuranCoreSnapshotRecord>(
"quran_core",
1,
);
for (const record of core.records) {
if (record.recordType === "verse") console.log(record.verseKey, record.textUthmani);
}Word-by-word transliterations use their resource content ID and expose a typed, camel-cased snapshot payload:
import type { WordByWordTransliterationSnapshotRecord } from "@quranjs/api";
await client.resources.sync({
bootstrap: true,
resources: "word_by_word_transliterations:60",
});
const transliterations =
await client.resources.findSnapshot<WordByWordTransliterationSnapshotRecord>(
"word_by_word_transliterations",
60,
);Links
- Quran Foundation — Our mission to make the Quran accessible to everyone
- API Documentation — Full API reference
- GitHub Repository — Source code and issues
License
MIT © Quran Foundation
