Home Features Discover Docs Discord App Store

Extend Pageturner

Pageturner is built on a fully extensible plugin architecture. Add your own metadata sources and download providers through simple JSON configurations — no code required.

Metadata Providers

Fetch book info — covers, descriptions, ratings, genres — and populate your home screen with discovery sections.

Download Sources

Search for audiobook files from any API — direct downloads, streaming links, or debrid-powered sources. Your configs, your sources.

Getting Started

Adding a new source to Pageturner takes just a few steps. Here's the quick version:

1

Write a JSON config

Define how Pageturner should call an API — the URL, response format, and field mappings. See the schemas below.

2

Import it into the app

Go to Settings → Metadata Providers or Settings → Download Sources and import via URL, paste JSON, or load a file.

3

Test your config

Use the built-in test button to verify your source works. Pageturner will show you latency, result counts, and any errors.

💡
Both metadata providers and download sources use the same import methods: URL (hosted JSON), JSON paste, or file import (.json file from your device).

Metadata Providers

Metadata providers tell Pageturner how to fetch book information from external APIs. They power two things: search (finding book metadata by title/author) and discovery (homepage sections like trending, new releases, etc.).

Pageturner ships with a built-in provider, but you can add your own to pull metadata from any API that returns JSON.

Schema Overview

A metadata provider config is a JSON object with these top-level fields:

FieldTypeRequiredDescription
idstringUnique identifier (lowercase, hyphens ok)
namestringDisplay name shown in the app
versionstringSemantic version (e.g. "1.0.0")
descriptionstringBrief description
iconstringURL to an icon image
capabilitiesobjectWhat this provider supports
authobjectAuthentication config
searchobject*Search endpoint config
discoverobject*Discovery sections config
rateLimitobjectRate limiting config

* Required if the corresponding capability is enabled.

Capabilities

The capabilities object declares what your provider supports:

JSON
{
  "capabilities": {
    "search": true,
    "discover": true
  }
}
  • search — Provider can search for books by query. Requires a search config.
  • discover — Provider can serve discovery sections for the home screen. Requires a discover config.

The search object defines how Pageturner calls the API and parses results:

JSON
"search": {
  "request": {
    "method": "GET",
    "url": "https://api.example.com/search?q={QUERY}",
    "headers": { "Accept": "application/json" },
    "timeout": 15000
  },
  "response": {
    "type": "json",
    "resultsPath": "data.books",
    "mapping": {
      "title": "name",
      "author": "writer",
      "cover": "coverUrl"
    }
  }
}

Response Field Mapping

Map fields from the API response to Pageturner's standard format. Use dot notation for nested fields (e.g. "data.author.name").

Mapping FieldDescription
titleBook title
subtitleSubtitle
authorPrimary author name
authorsArray of author names
narratorPrimary narrator name
narratorsArray of narrator names
coverCover image URL
descriptionBook description/synopsis
isbnISBN
asinASIN (Amazon/Audible ID)
publisherPublisher name
publishedYearYear published
durationDuration value
durationUnit"seconds" or "minutes" (default: seconds)
languageLanguage
genresArray of genre names
seriesNameSeries name
seriesPositionPosition in series
ratingAverage rating (0–5)
ratingsCountNumber of ratings

Discovery Configuration

Discovery sections appear on the home screen. Each section has its own request and optional response config:

JSON
"discover": {
  "sections": [
    {
      "id": "trending",
      "title": "Trending",
      "icon": "flame",
      "request": {
        "method": "GET",
        "url": "https://api.example.com/trending"
      },
      "response": {
        "type": "json",
        "resultsPath": "results",
        "mapping": {
          "title": "title",
          "author": "author",
          "cover": "cover"
        }
      }
    },
    {
      "id": "new-releases",
      "title": "New Releases",
      "icon": "sparkles",
      "request": { ... }
    }
  ]
}

If a discovery section omits its own response config, it inherits the mapping from the search config.

Template Variables

Use these placeholders in your request URLs and bodies — Pageturner substitutes them at runtime:

VariableDescriptionExample
{QUERY}User's search queryProject Hail Mary
{TITLE}Book titleProject Hail Mary
{AUTHOR}Author nameAndy Weir
{ISBN}ISBN if available978-0593135204
{ASIN}Audible ASINB08G9PRS1K
{USER_API_KEY}User-provided API key(from secure storage)
{PAGE}Current page number1

Authentication

If the API requires authentication, add an auth object:

JSON
"auth": {
  "type": "apiKey",        // "none" | "apiKey" | "bearer" | "basic"
  "headerName": "X-API-Key", // header to send the key in
  "userProvided": true       // user enters credentials in-app
}

When userProvided is true, the app prompts the user to enter their API key (stored in secure device storage).

Complete Example

Here's a complete metadata provider config for a fictional audiobook API:

my-provider.json
{
  "id": "my-audiobook-api",
  "name": "My Audiobook API",
  "version": "1.0.0",
  "description": "Custom metadata from my audiobook database",
  "capabilities": {
    "search": true,
    "discover": true
  },
  "auth": {
    "type": "apiKey",
    "headerName": "Authorization",
    "userProvided": true
  },
  "search": {
    "request": {
      "method": "GET",
      "url": "https://api.example.com/audiobooks/search?q={QUERY}",
      "timeout": 15000
    },
    "response": {
      "type": "json",
      "resultsPath": "data.books",
      "mapping": {
        "title": "title",
        "author": "author.name",
        "narrator": "narrator.name",
        "cover": "images.cover",
        "description": "synopsis",
        "genres": "categories",
        "duration": "lengthMinutes",
        "durationUnit": "minutes",
        "rating": "averageRating",
        "seriesName": "series.name",
        "seriesPosition": "series.position"
      }
    }
  },
  "discover": {
    "sections": [
      {
        "id": "trending",
        "title": "Trending",
        "icon": "flame",
        "request": {
          "method": "GET",
          "url": "https://api.example.com/audiobooks/trending"
        }
      },
      {
        "id": "new-releases",
        "title": "New Releases",
        "icon": "sparkles",
        "request": {
          "method": "GET",
          "url": "https://api.example.com/audiobooks/new"
        }
      }
    ]
  },
  "rateLimit": {
    "requestsPerMinute": 60,
    "retryAfterMs": 1000
  }
}

Importing a Provider

Three ways to add a metadata provider to Pageturner:

  • URL import — Host your JSON config at a public URL. In the app, go to Settings → Metadata Providers → Add → From URL.
  • JSON paste — Copy the JSON and paste it directly in the app's import dialog.
  • File import — Save a .json file to your device and import from Files.

Download Sources

Download sources (also called addons) tell Pageturner how to search for audiobook files from external APIs. When you browse a book and tap download, Pageturner queries all your enabled sources and shows the results.

Schema Overview

FieldTypeRequiredDescription
idstringUnique identifier (lowercase, hyphens ok)
namestringDisplay name
versionstringSemantic version
descriptionstringBrief description
iconstringIcon URL
typestring"torrent", "directDownload", or "stream"
legalobjectLegal status info
requestobjectHTTP request config
authobjectAuthentication config
responseobjectResponse parsing config
matchingobjectResult filtering config
rateLimitobjectRate limiting config
paginationobjectMulti-page results config

Source Types

  • "torrent" — Returns magnet links or hashes. Pageturner automatically routes these through your configured debrid service, which converts them into direct streams. Pageturner is not a torrent client — it relies on debrid services to resolve these into streamable links.
  • "directDownload" — Returns direct download URLs (HTTP links to audio files).
  • "stream" — Returns streaming URLs that can be played directly.

Legal Info

JSON
"legal": {
  "type": "public-domain",  // "public-domain" | "user-content" | "third-party"
  "disclaimer": "All content is in the public domain."
}

Request Configuration

JSON
"request": {
  "method": "GET",              // "GET" or "POST"
  "url": "https://api.example.com/search?q={TITLE}+{AUTHOR}",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {                     // for POST requests
    "query": "{TITLE} {AUTHOR}",
    "limit": 50
  },
  "timeout": 30000              // ms, default 30000, max 120000
}

Response Parsing

Tell Pageturner how to extract results from the JSON response:

JSON
"response": {
  "type": "json",
  "resultsPath": "hits",       // dot notation path to results array
  "mapping": {
    "title": "name",          // required
    "url": "downloadUrl",    // direct download link
    "magnetUrl": "magnet",     // magnet link (routed via debrid)
    "infoHash": "hash",       // hash (routed via debrid)
    "size": "fileSize",       // file size in bytes
    "seeders": "seeders",     // availability indicator
    "leechers": "leechers",   // availability indicator
    "quality": "quality",     // e.g. "320kbps"
    "format": "format",       // e.g. "M4B", "MP3"
    "source": "uploader",     // source/uploader name
    "date": "addedDate"       // upload date
  }
}

Result Matching

Pageturner can automatically filter results to find the best matches for the book you're looking for:

JSON
"matching": {
  "field": "title",       // field to match against
  "threshold": 0.7,        // 0.0 – 1.0, higher = stricter
  "algorithm": "fuzzy"      // "fuzzy" | "contains" | "exact"
}
  • "fuzzy" — Fuzzy string matching (recommended). Handles typos, word order differences.
  • "contains" — Result title must contain the search term.
  • "exact" — Exact match only.

Template Variables

VariableDescriptionExample
{TITLE}Audiobook titleProject Hail Mary
{AUTHOR}Author nameAndy Weir
{NARRATOR}Narrator nameRay Porter
{ASIN}Audible ASINB08G9PRS1K
{YEAR}Release year2021
{SERIES}Series nameThe Bobiverse
{SERIES_POSITION}Book number in series1
{ISBN}ISBN if available978-0593135204
{USER_API_KEY}User-provided API key(from secure storage)
{PAGE}Current page number1

Pagination

For APIs that return paginated results:

JSON
"pagination": {
  "type": "page",        // "page" | "offset" | "cursor"
  "paramName": "page",    // query parameter name
  "startAt": 1,            // first page value (0 or 1)
  "maxPages": 3,           // max pages to fetch (1–50)
  "hasMorePath": "meta.hasMore"  // JSON path to check for more
}

Built-in Sources

Pageturner ships with two built-in download sources for public domain content:

  • LibriVox — Free public domain audiobooks read by volunteers. Streams directly.
  • Internet Archive — Free audiobooks and audio from the Internet Archive. Streams directly.

These are always available and can be enabled/disabled in Settings → Download Sources.

Example Configs

Stream Source (Public API)

librivox-example.json
{
  "id": "librivox",
  "name": "LibriVox",
  "version": "1.0.0",
  "description": "Free public domain audiobooks read by volunteers",
  "type": "stream",
  "legal": {
    "type": "public-domain",
    "disclaimer": "All LibriVox recordings are in the public domain."
  },
  "request": {
    "method": "GET",
    "url": "https://librivox.org/api/feed/audiobooks?title={TITLE}&format=json",
    "timeout": 30000
  },
  "response": {
    "type": "json",
    "resultsPath": "books",
    "mapping": {
      "title": "title",
      "url": "url_zip_file",
      "source": "reader"
    }
  },
  "matching": {
    "field": "title",
    "threshold": 0.6,
    "algorithm": "fuzzy"
  }
}

Debrid Source (Authenticated API)

custom-indexer.json
{
  "id": "custom-indexer",
  "name": "Custom Indexer",
  "version": "1.0.0",
  "description": "My private indexer (uses debrid for streaming)",
  "type": "torrent",
  "legal": {
    "type": "third-party",
    "disclaimer": "User is responsible for content accessed through this source."
  },
  "request": {
    "method": "POST",
    "url": "https://api.example.com/search",
    "headers": { "Content-Type": "application/json" },
    "body": {
      "query": "{TITLE} {AUTHOR}",
      "limit": 50
    },
    "timeout": 30000
  },
  "auth": {
    "type": "apiKey",
    "headerName": "X-API-Key",
    "userProvided": true
  },
  "response": {
    "type": "json",
    "resultsPath": "hits",
    "mapping": {
      "title": "name",
      "magnetUrl": "magnet",
      "infoHash": "hash",
      "size": "size",
      "seeders": "seeders",
      "leechers": "leechers",
      "date": "added"
    }
  },
  "matching": {
    "field": "title",
    "threshold": 0.7,
    "algorithm": "fuzzy"
  },
  "rateLimit": {
    "requestsPerMinute": 30,
    "retryAfterMs": 2000
  },
  "pagination": {
    "type": "page",
    "paramName": "page",
    "startAt": 1,
    "maxPages": 3
  }
}

Importing a Source

Same three methods as metadata providers:

  • URL import — Host your JSON at a public URL. Settings → Download Sources → Add → From URL.
  • JSON paste — Paste the JSON directly in the import dialog.
  • File import — Load a .json file from your device.
ℹ️
After importing, use the Test button to verify your source works. Pageturner will show latency, result count, and any errors.

Debrid Services

Debrid services act as a middleman — they take magnet links from your download sources and convert them into direct streaming URLs. Pageturner is not a torrent client and never downloads torrents directly. Instead, it passes magnet links to your debrid provider, which handles everything and returns a streamable link.

Supported Providers

Setup

1

Get your API key

Sign up for a debrid service and generate an API key from their website.

2

Add it in Pageturner

Go to Settings → Debrid, select your provider, and paste your API key.

3

That's it

Magnet results from your download sources will now show a stream option. Pageturner sends them to your debrid provider and gets back a direct streaming link.

Cached content streams instantly. If the content isn't already cached on the debrid service, it may take a few minutes for the provider to prepare it. Pageturner checks cache status and shows it in the results.