@ctrl/plex

中文文档
8.0.0发布于 2026/10/16MITTypeScriptES Module构建来源已验证近 30 天下载 2155

Plex api client in typescript using ofetch

原始使用文档
文档目录7 节
  1. Install
  2. Docs
  3. Use
  4. Upgrading from v7
  5. Differences from python plex client
  6. Testing
  7. Running tests locally

@ctrl/plex npm

A TypeScript Plex API client using ofetch based on pkkid/python-plexapi

Install

npm install @ctrl/plex

Docs

https://plex.ep.workers.dev

Use

Create a plex connection

import { MyPlexAccount } from '@ctrl/plex';

const account = await new MyPlexAccount({
  baseUrl: 'http://localhost:32400',
  // Use username and password based auth
  username: 'username',
  password: 'password',
  // OR Token based auth
  token: 'token',
}).connect();

// List all available servers (resources) attached to your account
const resources = await account.resources();

// Connect to a specific server by name
// You can find the server name in the Plex Web UI settings or by listing resources above
const resource = await account.resource('<SERVERNAME>');
const plex = await resource.connect();
const library = await plex.library();

// List all libraries (Movies, TV Shows, Music, etc.)
const sections = await library.sections();

Identify your app. Plex lists every client by its product name and client identifier, so set your own before connecting and persist the identifier so Plex keeps seeing the same device.

import { setPlexHeaders } from '@ctrl/plex';

setPlexHeaders({
  product: 'My App',
  version: '1.0.0',
  identifier: 'replace-with-a-persisted-client-identifier',
});

Authenticate without collecting a Plex username or password. Persist the client identifier so it stays stable across logins.

import { MyPlexAccount, MyPlexPinLogin } from '@ctrl/plex';

const clientIdentifier = 'replace-with-a-persisted-client-identifier';
const login = await MyPlexPinLogin.create({ clientIdentifier, mode: 'oauth' });

console.log(`Open ${login.oauthUrl()} to sign in`);

const authentication = await login.wait({ timeout: 120_000 });
const account = await new MyPlexAccount({ token: authentication.token }).connect();

Create and refresh a signed Plex JWT. The bootstrap token must have been issued for the same client identifier. Store the returned credentials securely; they contain the private signing key.

import { MyPlexAccount, refreshPlexJwt, registerPlexJwt } from '@ctrl/plex';

let credentials = await registerPlexJwt({
  token: authentication.token,
  clientIdentifier,
  scopes: ['username', 'email', 'friendly_name'],
});

credentials = await refreshPlexJwt({ credentials });
const jwtAccount = await new MyPlexAccount({ token: credentials.token }).connect();
Example 1: List all unwatched movies.
import { MovieSection } from '@ctrl/plex';

// Pass MovieSection generic because the section title doesn't imply a section type.
const section = await library.section<MovieSection>('Movies');
// Get an array of Movie objects
const results = await section.search({ unwatched: true });
Example 2: Search for a list of movies containing a title
const library = await plex.library();
const section = await library.section<MovieSection>('Movies');
const results = await section.search({ title: 'Rush Hour' });
Example 3: List all content containing a specific query
const hubs = await plex.search('Arnold');
// Each hub is one category of results (movies, episodes, actors, ...)
for (const hub of hubs) {
  const [first] = await hub.items();
  console.log(hub.title, first?.title);
}
Example 4: List all episodes of a tv show.
import { ShowSection } from '@ctrl/plex';

// Pass ShowSection generic because the section title doesn't imply a section type.
const section = await library.section<ShowSection>('TV Shows');
// Get an array of Show objects
const results = await section.search({ title: 'Silicon Valley' });
const episodes = await results[0].episodes();
Example 5: Create a playlist from search results
import { Playlist } from '@ctrl/plex';

// The ten highest rated movies
const topMovies = await section.search({ sort: 'rating:desc', maxresults: 10 });

// Create a new playlist
const bestMovies = await Playlist.create(plex, 'Best Movies', { items: topMovies });

Upgrading from v7

v8 is a large release. The full list is in the release notes, the changes most likely to affect you:

  • Node.js 24 or newer is required.
  • Types are strict: fields Plex can omit are optional, and PlexServer.query() / MyPlexAccount.query() return unknown unless you pass a type argument.
  • Positional arguments became options objects, for example connect({ timeout }), removeItem(item, { refresh }), updateProgress({ time }) and account.device({ name }).
  • PlexClient commands take { type } instead of a positional mtype, and proxyThroughServer({ server }) replaces the _server / _proxyThroughServer fields.
  • Music sections list artists by default and onDeck() returns episodes and tracks.
  • Audio art and thumb are server paths, use artUrl / thumbUrl for token URLs.
  • plex.tv devices come from the JSON API, MyPlexDevice.id is a number.

Differences from python plex client

JS is a different language and some methods of the api were not possible. Chaining functions with requests must be awaited mostly individually. Constructors in JS don't typically make requests and accessing properties normally cannot make requests either. Methods take options objects instead of python style keyword arguments, and responses are requested as JSON.

Testing

Tests are run against a real instance of plex.

Create a plex account just for testing, using a real account will break everything. Put its credentials in a .env file at the root of the repo, the tests and scripts load it automatically.

PLEX_USERNAME=email
PLEX_PASSWORD=password
PLEXAPI_AUTH_SERVER_TOKEN=token

Setup test content (once)

pnpm run add-media

Run tests

pnpm test

Post testing, remove plex server from account. Warning this is destructive. Do not use this on a real plex account.

pnpm run test-cleanup

Running tests locally

Step 1

get a claim token from https://www.plex.tv/claim/ export PLEX_CLAIM_TOKEN=claim-token

Step 2

Start the plex container for testing. Replace /Users/scooper/gh/plex with the path to this repo's directory.

docker run -d \
  --name=plex \
  --net=host \
  -h orbstack \
  -p 32400:32400/tcp \
  -p 32400:32400 \
  -p 1900:1900/udp \
  -p 5353:5353/udp \
  -p 8324:8324 \
  -p 32410:32410/udp \
  -p 32412:32412/udp \
  -p 32413:32413/udp \
  -p 32414:32414/udp \
  -p 32469:32469 \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=Etc/UTC \
  -e VERSION=docker \
  -e PLEX_CLAIM=$PLEX_CLAIM_TOKEN \
  -v /Users/scooper/gh/plex/plex/media/db:/config \
  -v /Users/scooper/gh/plex/plex/media/transcode:/transcode \
  -v /Users/scooper/gh/plex/plex/media:/data \
  --restart unless-stopped \
  lscr.io/linuxserver/plex:latest

Pull latest plex container if needed

docker pull lscr.io/linuxserver/plex:latest

bootstrap plex server with test media. This uses PLEXAPI_AUTH_SERVER_TOKEN from .env.

pnpm run bootstraptest -- --no-docker --server-name=orbstack

版本与发布记录

@ctrl/plex 共发布 51 个版本,首次发布于 2020/07/26;过去 12 个月发布了 25 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
8.0.0latest2026/10/1628 天前
7.1.02026/09/082 个月前
7.0.12026/08/123 个月前
7.0.02026/08/113 个月前
6.7.02026/08/103 个月前
6.6.02026/08/103 个月前

关于 @ctrl/plex 的常见问题

@ctrl/plex 是什么?

Plex api client in typescript using ofetch

如何安装 @ctrl/plex?

在项目目录运行 npm install @ctrl/plex,或使用 pnpm add / yarn add / bun add。

@ctrl/plex 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ctrl/plex 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@ctrl/plex 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@ctrl/plex 可以商用吗?

@ctrl/plex 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ctrl/plex 的下载量有多少?

根据 npm 官方下载统计,@ctrl/plex 在 2026/10/11 – 2026/11/09 这 30 天内被下载 2,155 次,日均约 72 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ctrl/plex 还在维护吗?

最新版本 8.0.0 发布于 2026/10/16(28 天前);至今共发布 51 个版本,其中过去 12 个月发布了 25 个;首次发布于 2020/07/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。