10 Extensions
RockinChaos edited this page 2026-03-08 23:12:42 -07:00

🌐 Extensions

Extensions allow you to bring your own content sources into Shiru, such as a personal media server or any other legally owned media you host yourself.

Important

Shiru does not provide extensions, and the maintainers cannot provide you sources. Please do not ask.

Extensions are intended for accessing legally owned media only, such as content hosted on your own personal media server. Users are responsible for ensuring their use of any extension complies with all applicable laws.

🔎 Overview

What are extensions?

Extensions are JavaScript modules that connect Shiru to external content sources you control. Out of the box, Shiru plays files you already have locally. Extensions extend this by allowing Shiru to fetch content from sources like your own personal media server, making your library accessible from anywhere.

Does Shiru provide any extensions?

No. Shiru is a bring-your-own-content application and does not offer, recommend, or endorse any specific extensions or sources.

Is there a curated list of extensions?

No. Shiru is not directly associated with any extensions or sources. It is solely a client for managing and playing your own media.

Can I disable an extension without removing it?

Yes. Extensions can be toggled on or off individually from Settings > Extensions. Disabled extensions will not be queried, but are validated and remain added for easy re-enabling later.

⚙️ Adding & Managing Extensions

Navigate to Settings > Extensions > Sources to manage your extension sources.

dummyextension

A source entry points to an index.json manifest that describes one or more extensions. It can be added in three ways:

  • Direct URL - provide a full URL to an index.json, e.g. https://example.com/index.json
  • GitHub - use the format gh:username/repo or gh:username/repo/path if the manifest is in a subdirectory
  • npm - use the format npm:package-name

Shiru will automatically import all extensions the source provides (when the source manifest is not set as an extension repository), but you can individually disable extensions you do not want.

Extension Repositories

A single source can provide multiple extensions via an index.json that contains an array of entries, each with its own main path pointing to an extension source. This is useful for adding a repository of extensions that adds an Available Sources dropdown under the sources tab. You can then easily add which sources you want from the repository. The index.json that defines a repository would be as follows:

[
  {
    "main": "gh:username/repo/extension-one"
  },
  {
    "main": "gh:username/repo/extension-two"
  }
]

Adding gh:username/repo as a source will automatically import all sources defined in that repository's index.json but will not download the extensions in each source; you must manually click to add the source extensions.

🔧 Developing Extensions

Extensions are written in JavaScript and run in an isolated Web Worker. Any data you fetch must be CORS enabled.

Full type definitions, the abstract base class, and the reference implementation are available here.

Extensions load in parallel, so results from multiple extensions appear as each one completes rather than waiting for all to finish. Results are cached for approximately two minutes, so re-opening the search modal will not trigger another full fetch.

Structure Overview

An extension source consists of three parts:

  • index.json - the manifest describing your extension(s)
  • sources/your-extension.js - the extension logic, extending AbstractSource
  • index.d.ts - optional but recommended type definitions for IDE support

Testing Locally

You can specify a direct file path to your index.json instead of a URL during development. This lets you verify changes without hosting the extension remotely.

You can also load the official, fully functional reference extension via gh:RockinChaos/Shiru/extensions. It generates dummy results based on query parameters and is useful for understanding how extensions work end-to-end.

dummysource

Hosting on the Web

Extensions must be served as ESM-compatible JavaScript modules. If your extension is hosted as a plain .js file or npm package, you can use esm.sh to serve it as an ESM module without any build step:

https://esm.sh/gh/username/repo/my-extension

Both gh: and npm: prefixes are resolved through esm.sh, which serves the module as ESM-compatible JavaScript automatically. If you are pointing to a raw URL, ensure your server responds with Content-Type: application/javascript and includes the appropriate CORS headers.

Manifest - index.json

Each entry in your index.json describes one extension. A minimal example:

[
  {
    "id": "my-extension",
    "name": "My Extension",
    "version": "0.0.1",
    "main": "sources/my-extension",
    "update": "gh:username/repo",
    "type": "torrent",
    "speed": "fast",
    "accuracy": "high",
    "regions": ["US", "GB"],
    "description": "Connects to my personal media server.",
    "icon": "iVBORw0KGgoAAAANS..."
  }
]

The icon field accepts either a raw base64-encoded image string (without the data:image/...;base64, prefix) or a direct URL. Base64 is preferred as it avoids an extra network request and ensures the icon is always available, regardless of whether the source is reachable.

SourceConfig Fields

Field Type Description
id string Unique identifier for the extension. The more unique, the better
name string Display name shown in the UI
version string Semantic version, e.g. 0.0.1
main string Path to the extension module relative to the manifest. Supports gh:, npm:, or a direct URL
update string Path to the manifest used for update checks. Supports gh: and npm: prefixes
nsfw boolean? Set to true if the source may return NSFW results e.g. Hentai
unregulated boolean? Set to true if the source allows anonymous or unregistered uploads, which increases security risk
type 'torrent'? Source type. Currently only torrent is supported
speed 'fast' | 'moderate' | 'slow'? Estimated average fetch speed across various user locations. Do not factor in your own location
accuracy 'high' | 'medium' | 'low'? Likelihood that results match the requested series. Use high only for guaranteed matches
regions ServerLocations[]? ISO 3166-1 alpha-2 country codes representing the server node locations
deprecated boolean? Set to true when an extension is no longer maintained, or its source has shut down
description string? Short description shown in the UI. Markdown and basic HTML are supported
icon string? Raw base64-encoded image (without prefix) or URL. Base64 is recommended

RepositoryConfig Fields

If your index.json is acting as a repository, a list of pointers to other extension sources rather than defining extensions directly, each entry only needs a main field:

Field Type Description
main string Path to the extension source. Supports gh:username/repo/path, npm:package-name, or a direct URL

Extension Class

Extensions extend AbstractSource and must implement four methods: single, batch, movie, and validate.

import AbstractSource from './abstract.js'

export default new class MySource extends AbstractSource {
  url = 'https://your-source-url.com'

  async single(options) {
    // Return results for a single episode.
  }

  async batch(options) {
    // Return results for a full batch or season.
  }

  async movie(options) {
    // Return results for a movie.
    // If your source cannot distinguish movies from episodes, return [] and rely on single() instead.
  }

  async validate() {
    // Return true if the source is reachable.
    return (await fetch(this.url))?.ok
  }
}()

The validate() method supports failover logic; you can check multiple mirror URLs and update this.url to a working endpoint before queries begin:

async validate() {
  const mirrors = ['https://primary.example.com', 'https://backup.example.com']
  for (const mirror of mirrors) {
    if ((await fetch(mirror))?.ok) {
      this.url = mirror
      return true
    }
  }
  return false
}

Options Object

The options object is passed to single, batch, and movie as the first parameter.

Field Type Description
anilistId number AniList anime ID
media object Full AniList media entry for the requested series
mappingsA object? Anime-level cross-platform mapping data (AniDB, TVDB, IMDB, MVDB), not always present
mappingsE object? Episode-level mapping data, not always present
anidbAid number? AniDB anime ID, not always present
anidbEid number? AniDB episode ID, not always present
tvdbAid number? TVDB series ID, not always present
tvdbEid number? TVDB episode ID, not always present
imdbAid string? IMDB ID, not always present
mvdbAid number? TheMovieDB ID, not always present
titles string[] All known titles and alternative titles for the anime
episode number? Episode number to search for, not present for movies
episodeCount number? Total episode count, not always present
resolution '2160' | '1080' | '720' | '540' | '480' | '' Preferred video resolution. An empty string means any
exclusions string[] Keywords to exclude, such as unsupported codecs. Empty when an external player is enabled

Note

Mapping fields such as anidbAid, anidbEid, tvdbAid, tvdbEid, imdbAid, and mvdbAid significantly improve search accuracy when available. It is recommended to make use of them wherever possible.

Results Object

Each function must return a Promise<TorrentResult[]>. Each result should conform to the following:

Field Type Description
title string Content title. May represent multiple files in a batch
link string Direct http:// link to a content file, or a magnet: URI
id number? Optional source-specific ID
seeders number Number of seeders
leechers number Number of leechers
downloads number Total download count
accuracy 'high' | 'medium' | 'low'? Confidence that this result matches the requested episode
hash string Required. Info hash for the content
size number File size in bytes
date Date Upload date
type 'batch' | 'best' | 'alt'? Result classification. best and alt indicate relative quality ranking

Extension Deprecation

Extensions support a deprecated flag in the manifest to mark them as no longer maintained or as having had their source shut down. Deprecated extensions are visually indicated in the UI so users know to look for alternatives.

Tips for Extension Developers

  • Use anitomyscript to parse file names accurately, it is manually exposed to all extensions and can be accessed via this.anitomyscript without any import. This is especially useful for plain text searches where title matching is ambiguous
  • Always include both the original and any romanized or translated titles in your queries. Passing only a modified title can cause mismatches
  • Make use of anidbEid, tvdbEid, and the mapping objects wherever possible; they significantly improve accuracy for series with ambiguous or duplicate titles
  • The validate() method supports mirror failover: check multiple URLs and assign this.url to a working one before queries begin
  • For the movie() method, if your source cannot distinguish movies from episodes, return an empty array and rely on single() instead
  • Test locally using a direct file path to your index.json before publishing
  • Markdown and basic HTML are supported in extension description fields
  • The reference implementation at gh:RockinChaos/Shiru/extensions is fully functional and generates dummy results. Add it to explore how all parts of an extension fit together