Created/edited by GitHub Copilot; pending human review.
Fetch data or clean text
This flow uses two independent packages from the toolkit:
- The client fetches from Sefaria's API and checks each JSON response against the corrected API description. Use it when you don't have the data yet.
- The text tools turn Sefaria's HTML into safe HTML, keep footnotes separate, and choose which vowel and cantillation marks to show. Use them when you already hold Sefaria HTML.
Use both when you fetch and then display. Neither needs a browser or a DOM, so they run on a server.
Want ready-made UI? Use components.
Install
A community-driven project with Sefaria backing and support. The toolkit is experimental; names and APIs may change. License
Use the package route if your app already has a build step. Use the CDN route if you want no install step.
Packages
Use @sefaria/api-client, @sefaria/text-transform, or both. No registry token is needed. The packages need Node.js 22.12 or later.
npm install @sefaria/api-client@0.1.0-alpha.0 @sefaria/text-transform@0.1.0-alpha.0Install and status explains exact versions, the npm alpha tag, and CDN alternatives.
No install
Import the client and the text tools straight from the CDN. The client and the text tools are two ES-module files. Load them with type="module" or import. This needs no token and works in browser pages without an install step.
<script type="module">
import {
createSefariaClient,
text,
} from "https://cdn.jsdelivr.net/npm/@sefaria/api-client@0.1.0-alpha.0/dist/browser/sefaria-api-client.js";
import { normalizeText } from "https://cdn.jsdelivr.net/npm/@sefaria/text-transform@0.1.0-alpha.0/dist/browser/sefaria-text-transform.js";
</script>Import named functions, or use import * as client from "…" to take a whole file. A plain script tag without type="module" won't work.
These addresses pin 0.1.0-alpha.0. Install and status lists the exact UNPKG alternatives and the components' third file.
The examples below import the package names. With the CDN route, use the JavaScript tab of each example and replace each package name with its CDN address. To try an example without either route, press Run. The Run buttons on this page use exactly the CDN files.
Fetch one checked response
import { createSefariaClient, text } from "@sefaria/api-client";
const client = createSefariaClient();
// Fetch one passage. The client checks the response before you see it.
const { data, error, response } = await text.getV3Texts({
client,
path: { tref: "Micah 6:8" },
});
if (data === undefined) {
console.log(`Sefaria answered HTTP ${response.status}:`, error);
} else {
for (const version of data.versions) {
console.log(`${data.ref} · ${version.language} · ${version.versionTitle}`);
}
}
createSefariaClient() makes a client for https://www.sefaria.org. text.getV3Texts is the generated function for Sefaria's texts API (v3). With no version option, Sefaria returns the edition marked primary (isPrimary). Here that is the Hebrew Masoretic text.
On success, the result has data, and the example prints Micah 6:8 · he · Miqra according to the Masorah.
When Sefaria answers with a documented HTTP error, such as 404 for an unknown reference, the result has error with the documented error body. Then data is undefined. If a response doesn't match the API description, the call throws a SefariaContractError instead of returning data. Network failures also throw. They are not turned into empty data.
Learn more: Handle errors in your code · The client and Sefaria's API
Check JSON you already have
import {
createSefariaClient,
text,
validateExternalResponse,
} from "@sefaria/api-client";
// Get a response once and keep it as JSON text, as you would in a file or database.
const { data } = await text.getV3Texts({
client: createSefariaClient(),
path: { tref: "Micah 6:8" },
});
const stored = JSON.stringify(data);
// Later, read it back. Here one field is damaged on purpose to show a failed check.
const loaded: unknown = { ...JSON.parse(stored), isSpanning: "no" };
const check = validateExternalResponse(
{ method: "GET", path: "/api/v3/texts/{tref}", status: 200 },
loaded,
);
for (const issue of check.issues) {
console.log(`Invalid at ${issue.instancePath}: ${issue.message}`);
}
Use this for JSON that reaches you another way, such as from storage or a file. The example fetches once, stores the response as JSON text, reads it back, and damages one field on purpose.
validateExternalResponse returns { valid, issues } and does not throw. The example prints Invalid at /isSpanning: Invalid input: expected boolean, received string. Each issue's instancePath is a JSON Pointer into the checked value. It points to the place in the data. It doesn't say how to fix it.
Validating isn't cleaning. The check protects your code from a wrong response shape. It doesn't make the text safe to display. See Clean text and safety.
Learn more: Handle errors in your code · Clean text and safety
Clean text you already have
import {
applyVocalizationToHtml,
normalizeText,
} from "@sefaria/text-transform";
// Sefaria text you already have, for example from a file or a database.
const stored =
'הִגִּ֥יד לְךָ֛ אָדָ֖ם מַה־טּ֑וֹב <span class="mam-spi-samekh" onclick="alert(1)">{ס}</span>';
// 1. Make the markup safe first.
const { bodyHtml } = normalizeText(stored);
// 2. Then remove the cantillation marks and keep the vowels.
const withVowels = applyVocalizationToHtml(bodyHtml, "nikkud");
console.log(bodyHtml);
console.log(withVowels);
This makes no network request. It prints two lines: the normalized HTML, then the same HTML with the cantillation marks removed.
The first line shows what normalizeText did. It is the sanitizer. It removed the onclick attribute and turned Sefaria's mam-spi-samekh class into data-sefaria-mam="setumah", a marker for a closed paragraph break in the Masoretic text. It returns bodyHtml and notes. The notes field holds footnotes kept separately.
applyVocalizationToHtml(html, "nikkud") removes the cantillation marks (taamim) and keeps the vowel points (nikkud), as the second line shows. The vowel points are still there. It changes only text, not markup. Normalize first, then vocalize, because the vocalization helper does not make HTML safe. The other modes are taamim_and_nikkud and none. Clean up stored Sefaria text lists exactly which marks each mode removes.
Learn more: Clean up stored Sefaria text · Clean text and safety
Fetch and clean text
import { createSefariaClient, text } from "@sefaria/api-client";
import { normalizeText } from "@sefaria/text-transform";
const client = createSefariaClient();
const { data, error, response } = await text.getV3Texts({
client,
path: { tref: "Micah 6:8" },
query: { version: ["translation"] },
});
if (data === undefined) {
console.log(`Sefaria answered HTTP ${response.status}:`, error);
} else {
for (const version of data.versions) {
if (typeof version.text !== "string") continue;
const { bodyHtml, notes } = normalizeText(version.text);
console.log(`${version.versionTitle}:`);
console.log(bodyHtml);
console.log(`${notes.length} footnote(s) kept separately.`);
}
}
This example combines the client and the text tools. It asks for version: ["translation"], which returns Sefaria's default translation. Sefaria's default translation is not always English. For Micah 6:8 today it is "THE JPS TANAKH: Gender-Sensitive Edition". After you press Run, it prints the edition title, then the cleaned translation text, then a line saying 1 footnote(s) kept separately.
Sefaria's poetry <span class="..."> wrappers are gone. Ordinary formatting such as <small> and <br> stays. The footnote marker became an empty <span data-sefaria-note="0"> placeholder. Its content is in notes[0]. The example handles one verse. For a range of verses, Sefaria sends text as a list, so the example skips any text that isn't a string.
Learn more: How the toolkit works · Give components your own data