Build a Quran Translation Comparison Tool
This tutorial shows you how to fetch multiple Quran translations at the same time and display them side by side. It is a practical pattern for study apps, classroom tools, or any site that wants readers to see how different translators handled the same ayah.
What we are building
The end result is a small web page where a user picks a surah and ayah number, and the page shows English, Urdu, and French translations in three columns, with the Arabic text at the top. You can swap the translations for any languages UmmahAPI supports.
Here is a rough preview of what the output looks like:
The API endpoint you need
UmmahAPI has a single ayah endpoint that accepts a translations query parameter. You pass a comma-separated list of language codes and one request gives you all of them back. No need to loop through separate requests.
// Fetch surah 2, ayah 255 (Ayat al-Kursi) in three languages
// with Uthmani Arabic script included
const url = "https://ummahapi.com/api/quran/surah/2/ayah/255?translations=en,ur,fr&script=uthmani";
const res = await fetch(url);
const data = await res.json();
console.log(data);
The response includes a translations object with a key for each language code you requested, plus an arabic field when you add the script parameter. Check the full API docs for the exact response shape.
Tip. You can use ?script=tajweed instead of uthmani if you want color-coded tajweed markup in the Arabic text. Useful if your app targets readers learning to recite.
Step 1: The HTML skeleton
Start with a simple form and a container where the comparison cards will go. Keep the HTML minimal. All the interesting work happens in JavaScript.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Quran Translation Comparison</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Amiri&family=Inter:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Quran Translation Comparison</h1>
<form id="ayahForm">
<label>
Surah
<input type="number" id="surahInput" min="1" max="114" value="2">
</label>
<label>
Ayah
<input type="number" id="ayahInput" min="1" value="255">
</label>
<button type="submit">Compare</button>
</form>
<div id="status"></div>
<div id="output"></div>
</main>
<script src="app.js"></script>
</body>
</html>
Step 2: Fetch the translations
Create app.js. The fetch function takes a surah number and an ayah number, then calls the UmmahAPI endpoint with all three translations and the Uthmani script in one request.
// app.js
const BASE = "https://ummahapi.com/api";
const LANGS = "en,ur,fr"; // change or extend as needed
async function fetchAyah(surah, ayah) {
const url = `${BASE}/quran/surah/${surah}/ayah/${ayah}?translations=${LANGS}&script=uthmani`;
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Request failed: ${res.status}`);
}
return res.json();
}
const form = document.getElementById("ayahForm");
const output = document.getElementById("output");
const status = document.getElementById("status");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const surah = document.getElementById("surahInput").value;
const ayah = document.getElementById("ayahInput").value;
status.textContent = "Loading...";
output.innerHTML = "";
try {
const data = await fetchAyah(surah, ayah);
renderComparison(data);
status.textContent = "";
} catch (err) {
status.textContent = "Could not load that ayah. Check the surah and ayah numbers.";
console.error(err);
}
});
Step 3: Render the comparison cards
The renderComparison function takes the API response and builds the card layout. Each translation gets its own card. The Arabic card uses the Amiri font and right-to-left direction.
function renderComparison(data) {
const labelMap = {
en: "English",
ur: "Urdu",
fr: "French"
};
let html = `<div class="meta-bar">
<span>Surah ${data.surah_number}: ${data.surah_name_en}</span>
<span>Ayah ${data.ayah_number}</span>
</div>`;
html += '<div class="comparison-grid">';
// Arabic card first
if (data.arabic) {
html += `<div class="trans-card arabic-card">
<div class="lang-label">Arabic</div>
<div class="arabic-text">${data.arabic}</div>
</div>`;
}
// One card per translation
const translations = data.translations || {};
for (const [code, text] of Object.entries(translations)) {
const label = labelMap[code] || code.toUpperCase();
html += `<div class="trans-card">
<div class="lang-label">${label}</div>
<div class="trans-text">${text}</div>
</div>`;
}
html += '</div>';
output.innerHTML = html;
}
Step 4: Style the grid
A simple CSS grid handles the side-by-side layout. On mobile the cards stack. On wider screens they sit in columns.
/* style.css */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', sans-serif;
background: #f8fafc;
color: #1e293b;
padding: 2rem 1rem;
}
main { max-width: 900px; margin: 0 auto; }
h1 { font-size: 1.6rem; margin-bottom: 1.5rem; }
form { display: flex; gap: 0.75rem; align-items: flex-end; flex-wrap: wrap; margin-bottom: 2rem; }
label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.85rem; font-weight: 600; color: #475569; }
input[type="number"] {
padding: 0.5rem 0.75rem;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 1rem;
width: 90px;
}
button[type="submit"] {
padding: 0.55rem 1.25rem;
background: #16a34a;
color: #fff;
border: none;
border-radius: 6px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
}
button[type="submit"]:hover { background: #15803d; }
.meta-bar {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #64748b;
margin-bottom: 1rem;
font-weight: 600;
}
.comparison-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.trans-card {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 1.25rem;
}
.lang-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #64748b;
margin-bottom: 0.75rem;
}
.arabic-text {
font-family: 'Amiri', serif;
font-size: 1.5rem;
line-height: 2;
direction: rtl;
text-align: right;
}
.trans-text { font-size: 0.92rem; line-height: 1.8; color: #334155; }
#status { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; }
Adding a language selector
Hardcoding en,ur,fr works for demos, but a real tool should let users pick which translations to compare. Here is a simple checkbox approach. Each checkbox maps to a language code. The form reads which boxes are checked and builds the query string on the fly.
<!-- Add this above the form submit button -->
<fieldset>
<legend>Translations</legend>
<label><input type="checkbox" value="en" checked> English</label>
<label><input type="checkbox" value="ur" checked> Urdu</label>
<label><input type="checkbox" value="fr" checked> French</label>
</fieldset>
// Read checked boxes before fetching
const boxes = document.querySelectorAll('input[type="checkbox"]:checked');
const langs = [...boxes].map(b => b.value).join(",");
if (!langs) {
status.textContent = "Please select at least one translation.";
return;
}
const url = `${BASE}/quran/surah/${surah}/ayah/${ayah}?translations=${langs}&script=uthmani`;
const data = await (await fetch(url)).json();
Tip. If you are building this as a React or Vue component, move the fetchAyah call into a custom hook or a composable. Keep the fetch logic separate from the rendering so both are easy to test on their own.
Loading a random ayah
A "random ayah" button is a nice touch for study tools. UmmahAPI has a dedicated endpoint for this. You can call it, grab the surah and ayah number from the response, then pass those numbers into your normal fetch function.
async function loadRandom() {
const randomRes = await fetch("https://ummahapi.com/api/quran/random");
const randomData = await randomRes.json();
// Pull the identifiers from the random ayah response
const surah = randomData.surah_number;
const ayah = randomData.ayah_number;
// Update the inputs so the user can see what loaded
document.getElementById("surahInput").value = surah;
document.getElementById("ayahInput").value = ayah;
// Now fetch with translations
const data = await fetchAyah(surah, ayah);
renderComparison(data);
}
document.getElementById("randomBtn").addEventListener("click", loadRandom);
Add a button with id="randomBtn" next to your form and this just works. You can also use the random endpoint on page load so users see an ayah immediately without having to type anything.
Linking to tafsir from each card
Once a user picks an ayah, it makes sense to offer a link to the tafsir for deeper reading. UmmahAPI has a tafsir endpoint with three collections. You can add a small link at the bottom of each translation card that fetches ibn_kathir for that ayah.
// Fetch tafsir for the current ayah
async function fetchTafsir(surah, ayah) {
const url = `https://ummahapi.com/api/tafsir/ibn_kathir/surah/${surah}/ayah/${ayah}`;
const res = await fetch(url);
const data = await res.json();
return data.text || "";
}
// Call it when the user clicks a "Read Tafsir" button
document.getElementById("tafsirBtn").addEventListener("click", async () => {
const surah = document.getElementById("surahInput").value;
const ayah = document.getElementById("ayahInput").value;
const text = await fetchTafsir(surah, ayah);
document.getElementById("tafsirOutput").textContent = text;
});
See the Tafsir API page for the full list of available collections and what each one covers.
Getting an API key
The examples above work without any key. But the free key from ummahapi.com/register removes the rate limit completely. For a production app or anything with real traffic, grab the key and add it as a header.
const res = await fetch(url, {
headers: {
"X-API-Key": "your_key_here"
}
});
Keep the key in an environment variable if you are building a server-rendered app. Do not put it directly in client-side JavaScript that ships to browsers.
FAQ
Which translations does UmmahAPI support for comparison?
UmmahAPI supports multiple translations via the ?translations query parameter. You can request English, Urdu, and French in a single API call by passing ?translations=en,ur,fr to the ayah endpoint.
Can I fetch multiple translations in one API request?
Yes. The ayah endpoint accepts a comma-separated translations parameter. One request returns all the translations you asked for, so you do not need multiple round trips to the server.
Is there a rate limit for the Quran API?
Without an API key you get 5,000 requests per 15 minutes. Registering for a free key at ummahapi.com/register removes that limit entirely.
Can I show Arabic text alongside the translations?
Yes. Add ?script=uthmani, ?script=indopak, or ?script=tajweed to your request and the Arabic text comes back in the same response alongside your translations.
Build your Quran app with UmmahAPI
UmmahAPI is a free Islamic API with Quran, Hadith, Prayer Times, Qibla, and more, all in one place.
Read the docs