Skip to main content

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:

  1. Bootstrap once with bootstrap=true and a resources filter.
  2. Page until has_more is false, then store the final next_sync_token.
  3. Poll later with sync_token and the same resources filter.
  4. Apply row mutations directly.
  5. When a mutation has a non-null snapshot_url, fetch the snapshot for that resource_group and resource_id and 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.