All projects

BiblePath

A calm, free Bible reading tracker.

MinistryApp Store2026
View on the App Store

Problem

Most Bible apps want an account, push ads and analytics, and bury a simple daily habit under features. Keeping track of your reading should be calm, not one more thing selling to you.

Solution

BiblePath does one thing well. It tracks your Bible reading chapter by chapter, with plans, streaks and a daily reminder. It supports both the Protestant and Catholic canons, holds no Bible text, and keeps everything on your device, with no account, no ads and no analytics. And it is free.

What it is

A minimal iPhone app that tracks your Bible reading chapter by chapter, private and free.

Design process

01

General research

I wanted a calm and private way to keep a daily Bible reading habit. Most Bible apps ask for an account, push ads and analytics, and bury the simple thing under features.

02

Market validation

There are many Bible apps, but very few that just track your reading, stay private, and cost nothing. That gap is what I set out to fill.

03

Ideation

I decided BiblePath would track your reading chapter by chapter, support both the Protestant and Catholic canons, and keep everything on your device. Later I added a full offline reader, so you can read the text as well as track it.

04

Testing

I ran UX audits and recorded walkthrough videos of the app, and I turned what I saw into tracked lists of fixes.

05

Iteration

I iterated quickly from that video feedback, adding a verse image creator, verse of the day, search, highlights, a streak with a grace day, and a redesigned stats screen, across many small releases.

06

Build

I built the reader with three offline translations, reading plans with a custom builder, a reading journal with photos and voice notes, verse highlighting and sharing, and a backup and restore that you control.

07

Test

Before submitting I ran two adversarial code reviews and a full quality pass, checked the flows and that data persists, and verified a real build on the iOS simulator.

08

Release

BiblePath is live on the App Store, and I have shipped many updates, currently version 1.4.

Technical breakdown

TypeScriptReact NativeExpo SDK 57expo-routerAsyncStorageexpo-notificationsreact-native-reanimatedreact-native-svgEAS Build

BiblePath is a React Native app built with Expo, written in TypeScript. All of your data lives on the device in one saved state object, so there is no account and no backend. Your reading is stored by book and chapter, apart from which canon you are using. That way you can switch between the Protestant and the Catholic canon and never lose your progress, and the stats are simply recounted against whichever canon is active.

Data model

Storing reads by book and chapter, independent of canon

The Protestant canon has 66 books and 1,189 chapters, the Catholic canon has 73 books and 1,334 chapters. If I stored progress against the active canon, switching canon would silently destroy data. Instead reads are stored as readChapters keyed by book id and chapter, with no idea which canon is active, and every stat is recomputed against whichever canon you are in. Chapters that fall outside the active canon stay in storage and are simply left out of the totals. The lesson is to store the raw facts and derive every view from them, so a settings change can never corrupt the underlying data.

State

A store that survives React Native remounts

Reads kept resetting, because the state lived inside a provider that React Native was remounting. I moved all persisted state into a module level store that lives outside the component tree and survives remounts, exposed through a useApp hook, and I gate onboarding with Stack.Protected route guards rather than a Redirect, because a Redirect remounts the whole navigator and wipes the in memory state. Persistence is a debounced save with a flush when the app backgrounds, never a direct storage write from a screen. If an app loses state on navigation, look for a remount, not a missing save.

Content pipeline

Shipping three whole Bibles as offline JSON

The reader ships the King James, the Berean Standard, and the World English Bible as 77 per book JSON files generated from open source USFM text, so the whole thing works with no network. The Catholic Deuterocanonical books fall back to the World English Bible through a single effectiveTranslation function, so the reader never has a gap even where a translation does not cover a book. Precomputing content into the exact shape the app reads, instead of parsing at runtime, keeps a text heavy app fast and fully offline.

Reliability

Hardening a restore that accepts a file from outside

Backup restore is the one place the app takes in a file a stranger could have edited, so two adversarial reviews went at it. The importer validates every entry, for example an isValidJournalEntry check, and refuses a malformed file with a reason instead of writing partial or bad state into storage. Treat any imported or synced blob as hostile input, and validate it at the boundary the same way you would a network response.

Migration

Changing a default without overriding people who chose

When I moved the default reader font from 17pt sans to 23pt serif, I could not just change the default, because that would override everyone who had already picked their own. So a coerceStoredReader step detects only the users still sitting on the exact old default and moves just them, and leaves anyone who customised untouched. A default change is a migration, and the people who already made a choice are the ones you must not step on.

App Review

What the version 1.2 rejection actually was

Apple rejected version 1.2 over two concrete things, a background audio mode entitlement the app did not need, and price wording in the copy. I dropped the background audio, set enableBackgroundPlayback to false on the audio module, and removed the price language, and it was approved. Rejections are usually this specific, a named capability or a phrase, not a vague judgement.

Product

Reading plans that can never disagree with free reading

A plan pre generates its daily readings when you create it, but completion is read back from the same readChapters map that free reading writes to, so a chapter you read on your own counts toward the plan automatically, and the two can never drift apart. Only one plan is active at a time, enforced in the store. When two features touch the same idea, give them one source of truth rather than two states you have to keep in sync.

Privacy

No account, no analytics, everything local

There is no account, no ads, no analytics, and no backend. Everything lives on the device, the daily reminder is a local notification, and the App Store privacy answer is honestly no data collected. Local first is not only a privacy stance, it removes a whole class of sync bugs and outages you would otherwise have to build for.

Links