🧩
Seanime Extensions
🧩
Seanime Extensions
  • Seanime
    • Getting started
    • Core APIs
    • Changelog
  • Content Providers
    • Write, test, share
    • Anime torrent provider
    • Manga provider
    • Online streaming provider
  • Plugins
    • Introduction
    • Write, test, share
    • APIs
      • Helpers
      • Store
      • Storage
      • Database
      • AniList
      • System
        • Permissions
        • OS
        • Filepath
        • Commands
        • Buffers, I/O
        • MIME
    • UI
      • Basics
      • User Interface
        • Tray
        • Toast
        • Screen
        • Command Palette
        • Action
        • DOM
      • Anime/Library
        • Anime
        • Playback
        • Continuity
        • Auto Downloader
        • Auto Scanner
        • Filler Manager
        • External Player Link
      • Downloading
        • Downloader
        • Torrent Client
      • Other
        • Manga
        • Discord
        • MPV
    • Hooks
    • Example
  • Frequently asked
    • Feature requests
Powered by GitBook
On this page
  • 1. Create a JS/TS file
  • 2. Create a manifest file
  • 3. Quick overview
  • a. Permissions
  • b. Hooks
  • c. UI Context
  • d. Javascript restrictions
  • e. Types
  • 4. Write and test
  • Code the extension
  • Test it live
  • 5. Share
  1. Plugins

Write, test, share

How to start coding

1. Create a JS/TS file

Shortcut: Use bootstrapping command

You can use this third-party tool to help you quickly bootstrap a plugin folder locally

npx seanime-tool g-template
my-plugin.ts
function init() {
    // This function is called when the plugin is loaded
    // There is no guarantee as to when exactly the plugin will be loaded at startup
}

2. Create a manifest file

The ID should be unique, in case of a conflict with another extension, your plugin might not be loaded.

The name of the file should be the same as the ID.

Here we're going with Typescript.

We're setting isDevelopmentto true in order to be able to quickly reload it when we make changes. payloadURI in this case is the path to the plugin code, it must be an absolute path.

Obviously, before sharing the extension we'll change the payloadURI to the URL of the file containing the code and remove isDevelopment.

my-plugin.json
{
    "id": "my-plugin",
    "name": "My Plugin",
    "version": "1.0.0",
    "manifestURI": "",
    "language": "typescript",
    "type": "plugin",
    "description": "An example plugin",
    "author": "Seanime",
    "icon": "",
    "website": "",
    "lang": "multi",
    "payloadURI": "C:/path/to/my-plugin/code.ts",
    "plugin": {
        "version": "1",
        "permissions": {}
    },
    "isDevelopment": true
}

3. Quick overview

a. Permissions

Some APIs require specific permissions in order to function.

The user of your plugin will need to grant them after the installation.

b. Hooks

You can register hook callbacks to listen to various types of events happening on the server, modify them or execute custom logic. Learn more about hooks in later sections.

In a nutshell

Hooks can be used to listen to or edit server-side events.

For example:

Example
function init() {
   // This hook is triggered before Seanime formats the library data of an anime
   // The event contains the variables that Seanime will use, and you can modify them
   $app.onAnimeEntryLibraryDataRequested((e) => {
      // Setting this to an empty array will cause Seanime to think that the anime
      // has not been downloaded.
      e.entryLocalFiles = []
      
      e.next() // Continue hook chain
   })
}

Each hook handler must call e.next() in order for the hook chain listening to that event to proceed. Not calling it will impact other plugins listening to that event.

c. UI Context

Hooks are great for customizing server-side behavior but most business logic and interface interactions will be done in the UI context.

In a nutshell

Think of the UI context as the "main thread" of your plugin and hooks can be thought of as "worker threads".

Hooks and UI Context can be used alongside each other. In the later section you will learn how communication is done between them.

// A simple plugin that stores the history of scan durations
function init() {
    $app.onScanCompleted((e) => {
        // Store the scanning duration (in ms)
        $store.set("scan-completed", e.duration)
        
        e.next()
    })
    
    $ui.register((ctx) => {
    
        // Callback is triggered when the value is updated
        $store.watch<number>("scan-completed", (value) => {
            const now = new Date().toISOString().replaceall(".", "_")
            
            // Add the value to the history
            //   Note that this could have been done in the hook callback BUT
            //   the UI context is better suited for business logic
            $storage.set("scan-duration-history."+now, value)
            
            ctx.toast.info(`Scanning took ${value/1000} seconds!`)
        })
    })
}

d. Javascript restrictions

The UI context and each hook callback are run in isolated environments (called runtimes), and thus, cannot share state easily or read global variables.

const globalVar = 42;

function init() {
    const value = 42;
    
    $app.onGetAnime((e) => {
        console.log(globalVar) // undefined
        console.log(value) // undefined
    })
    
    $ui.register((ctx) => {
        console.log(globalVar) // undefined
        console.log(value) // undefined
    })
}

e. Types

Add the type definition files located here, in addition to core.d.ts

my-plugin.ts
/// <reference path="./plugin.d.ts" />
/// <reference path="./system.d.ts" />
/// <reference path="./app.d.ts" />
/// <reference path="./core.d.ts" />

function init() {
    // Everything is magically typed!
    
    $ui.register((ctx) => {
    
      ctx.dom.onReady(() => {
          console.log("Page loaded!")
      })
      
   })
}

4. Write and test

You're good to go!

Code the extension

Test it live

Because you've set isDevelopement to true in your manifest file, you will be able to manually reload the extension without having to restart the app. It's recommended to test your plugin with the web-app version of Seanime for convenience.

5. Share

Make sure to replace payloadURI with the URL of the hosted file containing the code.

Also, remove isDevelopment .

PreviousIntroductionNextAPIs

Last updated 1 month ago

However, you can still share variables between hooks and the UI context using .

In order to test your plugin, add the manifest file inside the extensions directory which is inside your .

If you want to share your plugin with others, you can host both the code and manifest file on GitHub and the link to the file.

$store
Store
APIs
UI
Hooks
data directory
share
https://raw.githubusercontent.com/5rahim/seanime/refs/heads/main/internal/extension_repo/goja_plugin_types/app.d.ts
app.d.ts
https://raw.githubusercontent.com/5rahim/seanime/refs/heads/main/internal/extension_repo/goja_plugin_types/plugin.d.ts
plugin.d.ts
https://raw.githubusercontent.com/5rahim/seanime/refs/heads/main/internal/extension_repo/goja_plugin_types/system.d.ts
system.d.ts
Diagram of plugin