I find regular need to quickly look up or copy and paste verses from the ESV Bible. Thankfully, the ESV API is available for free and easy to use. Using Raycast, I’ve created a script to automatically copy verses to my clipboard with a variety of formatting options. Here’s a preview:

Note: Since writing this post, I’ve created an extension for the ESV API you can download for free from the Raycast store. You will still need your own ESV API key as I show below.

Set up your ESV Account.

In order to call the API, you need both a free account with esv.org and permission granted to use the API with your application.

  1. Login to your ESV account. Go to api.esv.org, click the avatar, and choose Sign In.
    Sign into ESV account

  2. Sign in if you already have an account or click Create account if you need one.
    Sign into ESV account

  3. Click Create an API Application to request permission to use the API for your Raycast Script. Create a new ESV API application

  4. Provide details for your application. Here is an example. Depending on your needs, your application may require staff approval. Create a new ESV API application

  5. Once your application is approved, note your API key. You will need this to access the API. Note your ESV API key

Note: Treat your API key like a password. After creating this tutorial, I deleted this key and replaced it with an new one … so don’t go trying to use mine here 😏.

If you need access to your API key in the future, you can find it by logging in, clicking the avatar, and choosing My API Applications. All your approved applications should show in the list. Access your ESV API key

Download the Script files.

You can find the script files for this tutorial on my GitHub account.
Download Files

Note: If you’re unfamiliar with GitHub, the easiest way to download all the files is by clicking the green Code button and selecting Download ZIP.

Install NodeJS.

I wrote the script using NodeJS, which is a free server side JavaScript environment (no, you don’t need to know what that means to run the script). To get NodeJS on your machine, go to NodeJS.org and download the LTS version of Node.

Install it on your machine using normal program installation instructions.

Set up the Script.

1. Install the dependencies.

Open the folder you downloaded from GitHub using a code editor, like VS Code (You can download it for free here).

Next, open your Terminal and navigate to the folder. (If you’re using VSCode, just open the integrated Terminal from the main menu bar.)

Run the following command to install the dependencies: npm i

This will tell Node to install the dependencies you need for the script. It should look something like this: Install npm depedencies

2. Add your API key.

Next, create a new file in the folder called .env. This file will contain your API key. Add your API key to the file replacing my API key with your own. Install npm depedencies

Add the Script to Raycast.

You’ll need to tell Raycast where to find your script. To do this, open the Raycast Extensions.

  1. Open Raycast.
  2. Open the Raycast preferences ( + ,).
  3. Open the Extensions tab.
  4. Click the + icon and choose Add Script Directory. Install npm depedencies
  5. Navigate to your directory and click Open.
  6. Optionally, add an Alias (i.e., a shorthand in Raycast) or a Hotkey (i.e., globally invoke the script immediately with a keyboard shortcut). Install npm depedencies

Running the Script.

To run the script:

  1. Press your Raycast hotkey or open the Raycast menu and search for ESV passage.
  2. Tab to the passage field and type in the passage you want to look up.
  3. Add a desired format (optional)
  • All: default formatting from the ESV API
  • Some: removes some of the default formatting
  • None: removes all formatting (the default if you don’t specify a format)
  1. Press the Enter key to run the script.

Understanding / Modifying the Script

I’ll include the full script file that does all the calling of the API with comments throughout if you want to better understand or modify the script.

Note: Currently, iOS devices don’t display the following code block correctly—Sorry!

#!/usr/bin/env node

// Required parameters:
// @raycast.schemaVersion 1
// @raycast.title ESV Passage
// @raycast.mode silent

// Optional parameters:
// @raycast.icon assets/esv--light.png // 👈 the icon that shows in light mode
// @raycast.iconDark assets/esv--dark.png // 👈 the icon that shows in dark mode
// @raycast.packageName esv
// @raycast.argument1 { "type": "text", "placeholder": "Enter a Bible reference", "percentEncoded": true }
// @raycast.argument2 { "type": "text", "placeholder": "none (default), some, or all styling", "optional": true }

// Documentation:
// @raycast.description Call ESV API to copy text to your system clipboard
// @raycast.author Chris
// @raycast.authorURL @cpenned on Twitter

import dotenv from 'dotenv';
import fetch from 'node-fetch';
import clipboard from 'clipboardy';

// Load environment variables from .env file
const result = dotenv.config();
if (result.error) {
  throw result.error;
}
// Get user inputs for passages and styling
const passage = process.argv[2];
const style = process.argv[3].toLowerCase() || 'none'; // 👈 lowercase the user input so "All/Some/None" and "all/some/none" both work

// Show optional parameters from ESV API at https://api.esv.org/docs/passage-text/
// 👇 these are all set to the opposite of the default values set by the API
const nonDefaultStyling = {
  references: '&include-passage-references=false',
  verseNumbers: '&include-verse-numbers=false',
  firstVerseNumbers: '&include-first-verse-numbers=false',
  footnotes: '&include-footnotes=false',
  footnoteBody: '&include-footnote-body=false',
  headings: '&include-headings=false',
  shortCopyright: '&include-short-copyright=false',
  copyright: '&include-copyright=true',
  selahs: '&include-selahs=false',
  passageLines: '&include-passage-horizontal-lines=true',
  headingLines: '&include-heading-horizontal-lines=true',
  indentPoetry: '&indent-poetry=false',
  indentPoetryLines: '&indent-poetry-lines=0',
  indentDoxology: '&indent-psalm-doxology=0',
  indentDeclares: '&indent-declares=0',
  indentParagraphs: '&indent-paragraphs=0',
};

// Define selections for personal style groupings
// 👇 these are my preferred sets of params for each style group (i.e., all, some, none). You can change each to fit your needs.
const styleGroups = {
  none: `${nonDefaultStyling.references}${nonDefaultStyling.verseNumbers}${nonDefaultStyling.firstVerseNumbers}${nonDefaultStyling.footnotes}${nonDefaultStyling.footnoteBody}${nonDefaultStyling.shortCopyright}${nonDefaultStyling.headings}${nonDefaultStyling.indentPoetry}${nonDefaultStyling.indentPoetryLines}${nonDefaultStyling.indentDoxology}${nonDefaultStyling.indentDeclares}${nonDefaultStyling.indentParagraphs}`,
  some: `${nonDefaultStyling.footnotes}${nonDefaultStyling.footnoteBody}${nonDefaultStyling.shortCopyright}${nonDefaultStyling.headings}${nonDefaultStyling.indentPoetry}`,
  all: '', // default styling from ESV API
};

// Fetch passage from ESV API with user inputs, copy to clipboard, and notify user
fetch(`https://api.esv.org/v3/passage/text/?q=${passage}${styleGroups[style]}`, {
  method: 'GET',
  headers: {
    Authorization: `${process.env.API_TOKEN}`,
  },
})
  .then((response) => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then((data) => {
    clipboard.writeSync(data.passages[0].trim()); // 👈 get passage result, trim whitespace, and copy to clipboard
    console.log(`The passage is on your clipboard!`);
  })
  .catch((error) => {
    console.error('There has been a problem with your fetch operation:', error);
  });