Resources API
Purpose: Read content metadata such as translations, tafsirs, recitations, and languages.
Use this when: You have a backend and need Content API resource data.
Do not use this when: You only have frontend or mobile code with no backend.
Backend required: Yes.
Allowed runtimes: Node.js, serverless functions, workers.
Required credentials: client_id, client_secret, Content API access.
Minimal import: @quranjs/api/server.
Use @quranjs/api/server.
import { createServerClient } from "@quranjs/api/server";
const client = createServerClient({
clientId: process.env.QF_CLIENT_ID!,
clientSecret: process.env.QF_CLIENT_SECRET!,
});
const translations = await client.content.v4.resources.translations.list();
const tafsirs = await client.content.v4.resources.tafsirs.list();
const languages = await client.content.v4.resources.languages.list();
const chapterInfoResources =
await client.content.v4.resources.chapterInfos.list();
Use chapterInfos.list() as the global chapter-info resource catalog. For chapter-specific resource availability, prefer client.content.v4.chapters.getInfoResponse(chapterId, { includeResources: true }).
Resource Catalog Helpers
The server SDK exposes typed helpers for the public Content API resource catalogs.
const recitations = await client.content.v4.resources.recitations.list();
const recitationInfo =
await client.content.v4.resources.recitations.getInfo("7");
const translations = await client.content.v4.resources.translations.list();
const translationInfo =
await client.content.v4.resources.translations.getInfo("131");
const tafsirs = await client.content.v4.resources.tafsirs.list();
const tafsirInfo = await client.content.v4.resources.tafsirs.getInfo("169");
const chapterReciters =
await client.content.v4.resources.chapterReciters.list();
const recitationStyles =
await client.content.v4.resources.recitationStyles.list();
const verseMedia = await client.content.v4.resources.verseMedia.list();
Content Resource Sync
Use content resource sync when your app keeps a local copy of public Mushafs, tafsirs, translations, word-by-word translations, word-by-word transliterations, ayah recitations, chapter recitations, or articles and needs to update it without downloading every row again.
The flow is:
- Bootstrap once with
bootstrap=trueand aresourcesfilter. - Page until
has_moreis false, then store the finalnext_sync_token. - Poll later with
sync_tokenand the sameresourcesfilter. - Apply row mutations directly.
- When a mutation has a non-null
snapshot_url, fetch the snapshot for thatresource_groupandresource_idand replace that local resource.
The resources filter uses plural group names (articles, chapter_recitations, mushafs, recitations, tafsirs, translations, word_by_word_translations, word_by_word_transliterations). Use * for all public resources in a group or comma-separated IDs for specific resources, for example chapter_recitations:159;mushafs:1;translations:19,20;word_by_word_transliterations:60. Sync tokens are bound to the canonical resource filter, so use the same resources value for bootstrap and incremental requests.
chapter_recitations is keyed by Audio::Recitation.id and contains chapter
audio files. The legacy recitations group is keyed by the ayah-recitation
ID; its snapshots and compatible mutations may also include chapter audio for
existing clients.
Mushaf snapshots contain metadata, page mappings, and positioned words. Font files and images are not included. See Mushaf Fonts and Images.
const bootstrap = await client.content.v4.resources.sync({
bootstrap: true,
resources: "chapter_recitations:159;mushafs:1;word_by_word_transliterations:60",
});
const snapshot = await client.resources.findSnapshot("chapter_recitations", 159);
RESOURCE_UPDATE is only a resource-level freshness marker. It does not include snapshot_url; keep existing local rows unless row mutations, RESOURCE_INVALIDATE, or RESOURCE_DELETE later change them.
Word-by-Word Transliterations
Use the resource content ID with the word_by_word_transliterations group for
approved, shareable, one-word transliteration resources. This is separate from
the translation-only word_by_word_translations group.
import type { WordByWordTransliterationSnapshotRecord } from "@quranjs/api";
await client.content.v4.resources.sync({
bootstrap: true,
resources: "word_by_word_transliterations:60",
});
const snapshot =
await client.resources.findSnapshot<WordByWordTransliterationSnapshotRecord>(
"word_by_word_transliterations",
60,
);
for (const record of snapshot.records) {
console.log(record.wordId, record.text);
}
The SDK camel-cases the exact eight API fields to id, resourceContentId,
resourceId, wordId, languageId, languageName, text, and updatedAt.
Here resourceId == resourceContentId == ResourceContent.id identifies the
owning resource, while wordId identifies the normalized Word owner.
Snapshots are ordered by wordId, then id. Production resource 60
currently contains 77,431 records and includes row id: 1 with normalized
wordId: 60 and text "bis'mi".
Incremental row mutations use recordType: "word_transliteration".
Use the Content API reference for the sync endpoints:
Common Mistake
Do not call Resources from @quranjs/api/public. Content APIs are server-side for confidential clients.