IINA Plugin Development Skill
Build plugins for IINA, the modern macOS media player, using the React/TypeScript starter kit in this repository.
Architecture
IINA plugins have two execution environments:
- Entry scripts (
src/index.ts,src/global.ts) — Run in JavaScriptCore. No DOM. Access IINA APIs via the globaliinaobject. - React webviews (
src/ui/*/) — Run in WKWebView. Full DOM + React. Communicate with entry scripts viapostMessage/onMessage.
┌───────────────────────────────────────────────────┐
│ IINA Player │
│ ┌──────────────┐ postMessage ┌────────────┐ │
│ │ Entry Script │ ◄──────────────► │ Webview │ │
│ │ (JSContext) │ onMessage │ (React) │ │
│ │ │ │ │ │
│ │ iina.core │ │ Overlay │ │
│ │ iina.mpv │ │ Sidebar │ │
│ │ iina.event │ │ Window │ │
│ └──────────────┘ └────────────┘ │
└───────────────────────────────────────────────────┘
Workflow: Creating a New Plugin
- Copy this starter into a new directory
- Edit
Info.json: Setname,identifier,description,author,permissions - Edit
src/index.ts: Add IINA API logic (event listeners, mpv observers, webview comms) - Edit React UIs: Modify
src/ui/overlay/App.tsx,src/ui/sidebar/App.tsx, and/orsrc/ui/standalone/App.tsx - Wire the bridge: Use
sendToPlugin()andonPluginMessage()fromsrc/ui/bridge.ts - Add message types: Define new messages in
src/types.ts→EntryToWebviewMessagesandWebviewToEntryMessages - Build:
pnpm run build - Test:
pnpm run link→ restart IINA → check Log Viewer
Critical Rules
- Entry scripts have NO DOM. Never use
window,document,fetch,console.log. Useiina.console.log()instead. - React code has NO
iinaglobal. Usewindow.iina.postMessage()/window.iina.onMessage()via the bridge. - Always declare permissions in Info.json before using APIs that require them.
- Use mpv.observe() for status updates, not polling with setInterval.
- Two tsconfigs:
tsconfig.entry.json(no DOM) for entry scripts,tsconfig.ui.json(with DOM) for React.
Quick API Reference
| Module | Purpose | Key Methods |
|---|---|---|
iina.core |
Playback | open(), osd(), pause(), resume(), seek(), togglePause() |
iina.mpv |
mpv engine | getNumber(), getString(), getFlag(), set(), command(), observe() |
iina.event |
Events | on("iina.file-loaded", fn), on("iina.window-will-close", fn) |
iina.overlay |
Video overlay | loadFile(), show(), hide(), postMessage(), onMessage() |
iina.sidebar |
Sidebar tab | loadFile(), show(), postMessage(), onMessage() |
iina.standaloneWindow |
Window | open(), loadFile(), close(), setFrame(), postMessage(), onMessage() |
iina.http |
HTTP | get(), post() — needs network-request permission |
iina.file |
File I/O | read(), write(), exists(), trash() — needs file-system permission |
iina.menu |
Menus | addItem(title, action) |
iina.preferences |
Settings | get(key), set(key, value) |
iina.console |
Logging | log(), warn(), error() |
iina.global |
Cross-player | postMessage(playerID, name, data), onMessage(name, fn) |
Permissions
Declare in Info.json → permissions array:
show-osd— Display OSD messagesshow-alert— Show native alert dialogsvideo-overlay— Draw on video overlaynetwork-request— HTTP/WebSocket requestsfile-system— Read/write files, execute programs
Detailed References
- IINA API Reference — Complete API methods + event names
- Starter Structure — File tree with modification guidance
- Info.json Spec — All manifest fields and examples