3 <!-- START doctoc generated TOC please keep comment here to allow auto update -->
4 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
6 - [Concepts](#concepts)
8 - [Static files](#static-files)
10 - [Server API (only for plugins)](#server-api-only-for-plugins)
11 - [Settings](#settings)
13 - [Update video constants](#update-video-constants)
14 - [Add custom routes](#add-custom-routes)
15 - [Add external auth methods](#add-external-auth-methods)
16 - [Add new transcoding profiles](#add-new-transcoding-profiles)
17 - [Server helpers](#server-helpers)
18 - [Client API (themes & plugins)](#client-api-themes--plugins)
19 - [Plugin static route](#plugin-static-route)
20 - [Notifier](#notifier)
21 - [Markdown Renderer](#markdown-renderer)
22 - [Auth header](#auth-header)
23 - [Plugin router route](#plugin-router-route)
24 - [Custom Modal](#custom-modal)
25 - [Translate](#translate)
26 - [Get public settings](#get-public-settings)
27 - [Get server config](#get-server-config)
28 - [Add custom fields to video form](#add-custom-fields-to-video-form)
29 - [Register settings script](#register-settings-script)
30 - [Plugin selector on HTML elements](#plugin-selector-on-html-elements)
31 - [HTML placeholder elements](#html-placeholder-elements)
32 - [Add/remove left menu links](#addremove-left-menu-links)
33 - [Publishing](#publishing)
34 - [Write a plugin/theme](#write-a-plugintheme)
35 - [Clone the quickstart repository](#clone-the-quickstart-repository)
36 - [Configure your repository](#configure-your-repository)
37 - [Update README](#update-readme)
38 - [Update package.json](#update-packagejson)
39 - [Write code](#write-code)
40 - [Typescript](#typescript)
41 - [Add translations](#add-translations)
42 - [Build your plugin](#build-your-plugin)
43 - [Test your plugin/theme](#test-your-plugintheme)
45 - [Unpublish](#unpublish)
46 - [Plugin & Theme hooks/helpers API](#plugin--theme-hookshelpers-api)
48 - [Compatibility with PeerTube](#compatibility-with-peertube)
49 - [Spam/moderation plugin](#spammoderation-plugin)
50 - [Other plugin examples](#other-plugin-examples)
52 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
56 Themes are exactly the same as plugins, except that:
57 * Their name starts with `peertube-theme-` instead of `peertube-plugin-`
58 * They cannot declare server code (so they cannot register server hooks or settings)
59 * CSS files are loaded by client only if the theme is chosen by the administrator or the user
63 A plugin registers functions in JavaScript to execute when PeerTube (server and client) fires events. There are 3 types of hooks:
64 * `filter`: used to filter functions parameters or return values.
65 For example to replace words in video comments, or change the videos list behaviour
66 * `action`: used to do something after a certain trigger. For example to send a hook every time a video is published
67 * `static`: same than `action` but PeerTube waits their execution
69 On server side, these hooks are registered by the `library` file defined in `package.json`.
74 "library": "./main.js",
79 And `main.js` defines a `register` function:
84 async function register ({
100 registerExternalAuth,
101 unregisterExternalAuth,
102 registerIdAndPassAuth,
103 unregisterIdAndPassAuth
106 target: 'action:application.listening',
107 handler: () => displayHelloWorld()
112 Hooks prefixed by `action:api` also give access the original **express** [Request](http://expressjs.com/en/api.html#req) and [Response](http://expressjs.com/en/api.html#res):
115 async function register ({
117 peertubeHelpers: { logger }
120 target: 'action:api.video.updated',
121 handler: ({ req, res }) => logger.debug('original request parameters', { params: req.params })
127 On client side, these hooks are registered by the `clientScripts` files defined in `package.json`.
128 All client scripts have scopes so PeerTube client only loads scripts it needs:
135 "script": "client/common-client-plugin.js",
136 "scopes": [ "common" ]
139 "script": "client/video-watch-client-plugin.js",
140 "scopes": [ "video-watch" ]
147 And these scripts also define a `register` function:
150 function register ({ registerHook, peertubeHelpers }) {
152 target: 'action:application.init',
153 handler: () => onApplicationInit(peertubeHelpers)
160 Plugins can declare static directories that PeerTube will serve (images for example)
161 from `/plugins/{plugin-name}/{plugin-version}/static/`
162 or `/themes/{theme-name}/{theme-version}/static/` routes.
166 Plugins can declare CSS files that PeerTube will automatically inject in the client.
167 If you need to override existing style, you can use the `#custom-css` selector:
174 #custom-css .header {
175 background-color: red;
179 ### Server API (only for plugins)
183 Plugins can register settings, that PeerTube will inject in the administration interface.
184 The following fields will be automatically translated using the plugin translation files: `label`, `html`, `descriptionHTML`, `options.label`.
185 **These fields are injected in the plugin settings page as HTML, so pay attention to your translation files.**
190 function register (...) {
196 // type: 'input' | 'input-checkbox' | 'input-password' | 'input-textarea' | 'markdown-text' | 'markdown-enhanced' | 'select' | 'html'
198 // If type: 'select', give the select available options
200 { label: 'Label 1', value: 'value1' },
201 { label: 'Label 2', value: 'value2' }
204 // If type: 'html', set the HTML that will be injected in the page
205 html: '<strong class="...">Hello</strong><br /><br />'
208 descriptionHTML: 'The purpose of this field is...',
210 default: 'my super name',
212 // If the setting is not private, anyone can view its value (client code included)
213 // If the setting is private, only server-side hooks can access it
217 const adminName = await settingsManager.getSetting('admin-name')
219 const result = await settingsManager.getSettings([ 'admin-name', 'admin-password' ])
222 settingsManager.onSettingsChange(settings => {
223 settings['admin-name']
230 Plugins can store/load JSON data, that PeerTube will store in its database (so don't put files in there).
238 const value = await storageManager.getData('mykey')
239 await storageManager.storeData('mykey', { subkey: 'value' })
243 You can also store files in the plugin data directory (`/{plugins-directory}/data/{npm-plugin-name}`) **in PeerTube >= 3.2**.
244 This directory and its content won't be deleted when your plugin is uninstalled/upgraded.
251 const basePath = peertubeHelpers.plugin.getDataDirectoryPath()
253 fs.writeFile(path.join(basePath, 'filename.txt'), 'content of my file', function (err) {
259 #### Update video constants
261 You can add/delete video categories, licences or languages using the appropriate constant managers:
265 videoLanguageManager,
266 videoCategoryManager,
269 playlistPrivacyManager
271 videoLanguageManager.addConstant('al_bhed', 'Al Bhed')
272 videoLanguageManager.deleteConstant('fr')
274 videoCategoryManager.addConstant(42, 'Best category')
275 videoCategoryManager.deleteConstant(1) // Music
276 videoCategoryManager.resetConstants() // Reset to initial categories
277 videoCategoryManager.getConstants() // Retrieve all category constants
279 videoLicenceManager.addConstant(42, 'Best licence')
280 videoLicenceManager.deleteConstant(7) // Public domain
282 videoPrivacyManager.deleteConstant(2) // Remove Unlisted video privacy
283 playlistPrivacyManager.deleteConstant(3) // Remove Private video playlist privacy
287 #### Add custom routes
289 You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin:
295 const router = getRouter()
296 router.get('/ping', (req, res) => res.json({ message: 'pong' }))
298 // Users are automatically authenticated
299 router.get('/auth', async (res, res) => {
300 const user = await peertubeHelpers.user.getAuthUser(res)
302 const isAdmin = user.role === 0
303 const isModerator = user.role === 1
304 const isUser = user.role === 2
307 username: user.username,
316 The `ping` route can be accessed using:
317 * `/plugins/:pluginName/:pluginVersion/router/ping`
318 * Or `/plugins/:pluginName/router/ping`
321 #### Add external auth methods
323 If you want to add a classic username/email and password auth method (like [LDAP](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-ldap) for example):
326 function register (...) {
328 registerIdAndPassAuth({
329 authName: 'my-auth-method',
331 // PeerTube will try all id and pass plugins in the weight DESC order
332 // Exposing this value in the plugin settings could be interesting
335 // Optional function called by PeerTube when the user clicked on the logout button
337 console.log('User %s logged out.', user.username')
340 // Optional function called by PeerTube when the access token or refresh token are generated/refreshed
341 hookTokenValidity: ({ token, type }) => {
342 if (type === 'access') return { valid: true }
343 if (type === 'refresh') return { valid: false }
346 // Used by PeerTube when the user tries to authenticate
347 login: ({ id, password }) => {
348 if (id === 'user' && password === 'super password') {
351 email: 'user@example.com'
353 displayName: 'User display name'
362 // Unregister this auth method
363 unregisterIdAndPassAuth('my-auth-method')
367 You can also add an external auth method (like [OpenID](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-openid-connect), [SAML2](https://framagit.org/framasoft/peertube/official-plugins/-/tree/master/peertube-plugin-auth-saml2) etc):
370 function register (...) {
372 // result contains the userAuthenticated auth method you can call to authenticate a user
373 const result = registerExternalAuth({
374 authName: 'my-auth-method',
376 // Will be displayed in a button next to the login form
377 authDisplayName: () => 'Auth method'
379 // If the user click on the auth button, PeerTube will forward the request in this function
380 onAuthRequest: (req, res) => {
381 res.redirect('https://external-auth.example.com/auth')
384 // Same than registerIdAndPassAuth option
387 // Same than registerIdAndPassAuth option
388 // hookTokenValidity: ...
391 router.use('/external-auth-callback', (req, res) => {
392 // Forward the request to PeerTube
393 result.userAuthenticated({
397 email: 'user@example.com'
399 displayName: 'User display name'
403 // Unregister this external auth method
404 unregisterExternalAuth('my-auth-method)
408 #### Add new transcoding profiles
410 Adding transcoding profiles allow admins to change ffmpeg encoding parameters and/or encoders.
411 A transcoding profile has to be chosen by the admin of the instance using the admin configuration.
414 async function register ({
418 // Adapt bitrate when using libx264 encoder
420 const builder = (options) => {
421 const { input, resolution, fps, streamNum } = options
423 const streamString = streamNum ? ':' + streamNum : ''
425 // You can also return a promise
426 // All these options are optional
429 // Used to define an alternative scale filter, needed by some encoders
430 // Default to 'scale'
437 // Use a custom bitrate
438 '-b' + streamString + ' 10K'
443 const encoder = 'libx264'
444 const profileName = 'low-quality'
446 // Support this profile for VOD transcoding
447 transcodingManager.addVODProfile(encoder, profileName, builder)
449 // And/Or support this profile for live transcoding
450 transcodingManager.addLiveProfile(encoder, profileName, builder)
454 const builder = (options) => {
455 const { streamNum } = options
457 const streamString = streamNum ? ':' + streamNum : ''
459 // Always copy stream when PeerTube use libfdk_aac or aac encoders
465 const profileName = 'copy-audio'
467 for (const encoder of [ 'libfdk_aac', 'aac' ]) {
468 transcodingManager.addVODProfile(encoder, profileName, builder)
473 PeerTube will try different encoders depending on their priority.
474 If the encoder is not available in the current transcoding profile or in ffmpeg, it tries the next one.
475 Plugins can change the order of these encoders and add their custom encoders:
478 async function register ({
482 // Adapt bitrate when using libx264 encoder
484 const builder = () => {
491 // Support libopus and libvpx-vp9 encoders (these codecs could be incompatible with the player)
492 transcodingManager.addVODProfile('libopus', 'test-vod-profile', builder)
494 // Default priorities are ~100
495 // Lowest priority = 1
496 transcodingManager.addVODEncoderPriority('audio', 'libopus', 1000)
498 transcodingManager.addVODProfile('libvpx-vp9', 'test-vod-profile', builder)
499 transcodingManager.addVODEncoderPriority('video', 'libvpx-vp9', 1000)
501 transcodingManager.addLiveProfile('libopus', 'test-live-profile', builder)
502 transcodingManager.addLiveEncoderPriority('audio', 'libopus', 1000)
506 During live transcode input options are applied once for each target resolution.
507 Plugins are responsible for detecting such situation and applying input options only once if necessary.
511 PeerTube provides your plugin some helpers. For example:
514 async function register ({
519 const serverActor = await peertubeHelpers.server.getServerActor()
521 await peertubeHelpers.moderation.blockServer({ byAccountId: serverActor.Account.id, hostToBlock: '...' })
526 const video = await peertubeHelpers.videos.loadByUrl('...')
531 See the [plugin API reference](https://docs.joinpeertube.org/api-plugins) to see the complete helpers list.
533 ### Client API (themes & plugins)
535 #### Plugin static route
537 To get your plugin static route:
540 function register (...) {
541 const baseStaticUrl = peertubeHelpers.getBaseStaticRoute()
542 const imageUrl = baseStaticUrl + '/images/chocobo.png'
548 To notify the user with the PeerTube ToastModule:
551 function register (...) {
552 const { notifier } = peertubeHelpers
553 notifier.success('Success message content.')
554 notifier.error('Error message content.')
558 #### Markdown Renderer
560 To render a formatted markdown text to HTML:
563 function register (...) {
564 const { markdownRenderer } = peertubeHelpers
566 await markdownRenderer.textMarkdownToHTML('**My Bold Text**')
567 // return <strong>My Bold Text</strong>
569 await markdownRenderer.enhancedMarkdownToHTML('![alt-img](http://.../my-image.jpg)')
570 // return <img alt=alt-img src=http://.../my-image.jpg />
578 To make your own HTTP requests using the current authenticated user, use an helper to automatically set appropriate headers:
581 function register (...) {
583 target: 'action:auth-user.information-loaded',
584 handler: ({ user }) => {
586 // Useless because we have the same info in the ({ user }) parameter
587 // It's just an example
588 fetch('/api/v1/users/me', {
590 headers: peertubeHelpers.getAuthHeader()
591 }).then(res => res.json())
592 .then(data => console.log('Hi %s.', data.username))
598 #### Plugin router route
602 To get your plugin router route, you can use `peertubeHelpers.getBaseRouterRoute()`:
605 function register (...) {
607 target: 'action:video-watch.video.loaded',
608 handler: ({ video }) => {
609 fetch(peertubeHelpers.getBaseRouterRoute() + '/my/plugin/api', {
611 headers: peertubeHelpers.getAuthHeader()
612 }).then(res => res.json())
613 .then(data => console.log('Hi %s.', data))
621 To show a custom modal:
624 function register (...) {
625 peertubeHelpers.showModal({
626 title: 'My custom modal title',
627 content: '<p>My custom modal content</p>',
628 // Optionals parameters :
631 // show cancel button and call action() after hiding modal
632 cancel: { value: 'cancel', action: () => {} },
633 // show confirm button and call action() after hiding modal
634 confirm: { value: 'confirm', action: () => {} },
641 You can translate some strings of your plugin (PeerTube will use your `translations` object of your `package.json` file):
644 function register (...) {
645 peertubeHelpers.translate('User name')
646 .then(translation => console.log('Translated User name by ' + translation))
650 #### Get public settings
652 To get your public plugin settings:
655 function register (...) {
656 peertubeHelpers.getSettings()
658 if (!s || !s['site-id'] || !s['url']) {
659 console.error('Matomo settings are not set.')
668 #### Get server config
671 function register (...) {
672 peertubeHelpers.getServerConfig()
674 console.log('Fetched server config.', config)
679 #### Add custom fields to video form
681 To add custom fields in the video form (in *Plugin settings* tab):
684 async function register ({ registerVideoField, peertubeHelpers }) {
685 const descriptionHTML = await peertubeHelpers.translate(descriptionSource)
686 const commonOptions = {
687 name: 'my-field-name,
688 label: 'My added field',
689 descriptionHTML: 'Optional description',
691 // type: 'input' | 'input-checkbox' | 'input-password' | 'input-textarea' | 'markdown-text' | 'markdown-enhanced' | 'select' | 'html'
692 // /!\ 'input-checkbox' could send "false" and "true" strings instead of boolean
693 type: 'input-textarea',
696 // Optional, to hide a field depending on the current form state
697 // liveVideo is in the options object when the user is creating/updating a live
698 // videoToUpdate is in the options object when the user is updating a video
699 hidden: ({ formValues, videoToUpdate, liveVideo }) => {
700 return formValues.pluginData['other-field'] === 'toto'
704 for (const type of [ 'upload', 'import-url', 'import-torrent', 'update', 'go-live' ]) {
705 registerVideoField(commonOptions, { type })
710 PeerTube will send this field value in `body.pluginData['my-field-name']` and fetch it from `video.pluginData['my-field-name']`.
712 So for example, if you want to store an additional metadata for videos, register the following hooks in **server**:
715 async function register ({
719 const fieldName = 'my-field-name'
721 // Store data associated to this video
723 target: 'action:api.video.updated',
724 handler: ({ video, body }) => {
725 if (!body.pluginData) return
727 const value = body.pluginData[fieldName]
730 storageManager.storeData(fieldName + '-' + video.id, value)
734 // Add your custom value to the video, so the client autofill your field using the previously stored value
736 target: 'filter:api.video.get.result',
737 handler: async (video) => {
738 if (!video) return video
739 if (!video.pluginData) video.pluginData = {}
741 const result = await storageManager.getData(fieldName + '-' + video.id)
742 video.pluginData[fieldName] = result
750 #### Register settings script
752 To hide some fields in your settings plugin page depending on the form state:
755 async function register ({ registerSettingsScript }) {
756 registerSettingsScript({
757 isSettingHidden: options => {
758 if (options.setting.name === 'my-setting' && options.formValues['field45'] === '2') {
767 #### Plugin selector on HTML elements
769 PeerTube provides some selectors (using `id` HTML attribute) on important blocks so plugins can easily change their style.
771 For example `#plugin-selector-login-form` could be used to hide the login form.
773 See the complete list on https://docs.joinpeertube.org/api-plugins
775 #### HTML placeholder elements
777 PeerTube provides some HTML id so plugins can easily insert their own element:
780 async function register (...) {
781 const elem = document.createElement('div')
782 elem.className = 'hello-world-h4'
783 elem.innerHTML = '<h4>Hello everybody! This is an element next to the player</h4>'
785 document.getElementById('plugin-placeholder-player-next').appendChild(elem)
789 See the complete list on https://docs.joinpeertube.org/api-plugins
791 #### Add/remove left menu links
793 Left menu links can be filtered (add/remove a section or add/remove links) using the `filter:left-menu.links.create.result` client hook.
798 PeerTube plugins and themes should be published on [NPM](https://www.npmjs.com/) so that PeerTube indexes take into account your plugin (after ~ 1 day). An official plugin index is available on [packages.joinpeertube.org](https://packages.joinpeertube.org/api/v1/plugins), with no interface to present packages.
800 > The official plugin index source code is available at https://framagit.org/framasoft/peertube/plugin-index
802 ## Write a plugin/theme
805 * Find a name for your plugin or your theme (must not have spaces, it can only contain lowercase letters and `-`)
806 * Add the appropriate prefix:
807 * If you develop a plugin, add `peertube-plugin-` prefix to your plugin name (for example: `peertube-plugin-mysupername`)
808 * If you develop a theme, add `peertube-theme-` prefix to your theme name (for example: `peertube-theme-mysupertheme`)
809 * Clone the quickstart repository
810 * Configure your repository
812 * Update `package.json`
813 * Register hooks, add CSS and static files
814 * Test your plugin/theme with a local PeerTube installation
815 * Publish your plugin/theme on NPM
817 ### Clone the quickstart repository
819 If you develop a plugin, clone the `peertube-plugin-quickstart` repository:
822 $ git clone https://framagit.org/framasoft/peertube/peertube-plugin-quickstart.git peertube-plugin-mysupername
825 If you develop a theme, clone the `peertube-theme-quickstart` repository:
828 $ git clone https://framagit.org/framasoft/peertube/peertube-theme-quickstart.git peertube-theme-mysupername
831 ### Configure your repository
833 Set your repository URL:
836 $ cd peertube-plugin-mysupername # or cd peertube-theme-mysupername
837 $ git remote set-url origin https://your-git-repo
842 Update `README.md` file:
848 ### Update package.json
850 Update the `package.json` fields:
851 * `name` (should start with `peertube-plugin-` or `peertube-theme-`)
856 * `engine.peertube` (the PeerTube version compatibility, must be `>=x.y.z` and nothing else)
858 **Caution:** Don't update or remove other keys, or PeerTube will not be able to index/install your plugin.
859 If you don't need static directories, use an empty `object`:
869 And if you don't need CSS or client script files, use an empty `array`:
882 Now you can register hooks or settings, write CSS and add static directories to your plugin or your theme :)
884 **Caution:** It's up to you to check the code you write will be compatible with the PeerTube NodeJS version,
885 and will be supported by web browsers.
886 If you want to write modern JavaScript, please use a transpiler like [Babel](https://babeljs.io/).
887 If you want to use __Typescript__ see section below.
891 You can add __PeerTube__ types as dev dependencies:
893 npm install --save-dev @peertube/peertube-types
896 This package exposes *server* definition files by default:
898 import { RegisterServerOptions } from '@peertube/peertube-types/server/types'
900 export async function register ({ registerHook }: RegisterServerOptions) {
902 target: 'action:application.listening',
903 handler: () => displayHelloWorld()
908 But it also exposes client types and various models used in __PeerTube__:
910 import { RegisterClientOptions } from '@larriereguichet/peertube-types/client/types';
911 import { Video } from '@larriereguichet/peertube-types/shared';
913 function register({ registerHook, peertubeHelpers }: RegisterClientOptions) {
915 target: 'action:admin-plugin-settings.init',
916 handler: ({ npmName }: { npmName: string }) => {
917 if ('peertube-plugin-transcription' !== npmName) {
924 target: 'action:video-watch.video.loaded',
925 handler: ({ video }: { video: Video }) => {
926 fetch(`${peertubeHelpers.getBaseRouterRoute()}/videos/${video.uuid}/captions`, {
928 headers: peertubeHelpers.getAuthHeader(),
930 .then((res) => res.json())
931 .then((data) => console.log('Hi %s.', data));
938 > Other types are accessible from the shared path `@peertube/peertube-types/shared`.
942 If you want to translate strings of your plugin (like labels of your registered settings), create a file and add it to `package.json`:
948 "fr": "./languages/fr.json",
949 "pt-BR": "./languages/pt-BR.json"
955 The key should be one of the locales defined in [i18n.ts](https://github.com/Chocobozzz/PeerTube/blob/develop/shared/models/i18n/i18n.ts).
957 Translation files are just objects, with the english sentence as the key and the translation as the value.
958 `fr.json` could contain for example:
962 "Hello world": "Hello le monde"
966 ### Build your plugin
968 If you added client scripts, you'll need to build them using webpack.
976 Add/update your files in the `clientFiles` array of `webpack.config.js`:
979 $ $EDITOR ./webpack.config.js
982 Build your client files:
988 You built files are in the `dist/` directory. Check `package.json` to correctly point to them.
991 ### Test your plugin/theme
993 You'll need to have a local PeerTube instance:
994 * Follow the [dev prerequisites](https://github.com/Chocobozzz/PeerTube/blob/develop/.github/CONTRIBUTING.md#prerequisites)
995 (to clone the repository, install dependencies and prepare the database)
996 * Build PeerTube (`--light` to only build the english language):
999 $ npm run build -- --light
1008 * Run PeerTube (you can access to your instance on http://localhost:9000):
1011 $ NODE_ENV=test npm start
1014 * Register the instance via the CLI:
1017 $ node ./dist/server/tools/peertube.js auth add -u 'http://localhost:9000' -U 'root' --password 'test'
1020 Then, you can install or reinstall your local plugin/theme by running:
1023 $ node ./dist/server/tools/peertube.js plugins install --path /your/absolute/plugin-or-theme/path
1028 Go in your plugin/theme directory, and run:
1034 Every time you want to publish another version of your plugin/theme, just update the `version` key from the `package.json`
1035 and republish it on NPM. Remember that the PeerTube index will take into account your new plugin/theme version after ~24 hours.
1037 > If you need to force your plugin update on a specific __PeerTube__ instance, you may update the latest available version manually:
1039 > UPDATE "plugin" SET "latestVersion" = 'X.X.X' WHERE "plugin"."name" = 'plugin-shortname';
1041 > You'll then be able to click the __Update plugin__ button on the plugin list.
1045 If for a particular reason you don't want to maintain your plugin/theme anymore
1046 you can deprecate it. The plugin index will automatically remove it preventing users to find/install it from the PeerTube admin interface:
1049 $ npm deprecate peertube-plugin-xxx@"> 0.0.0" "explain here why you deprecate your plugin/theme"
1052 ## Plugin & Theme hooks/helpers API
1054 See the dedicated documentation: https://docs.joinpeertube.org/api-plugins
1059 ### Compatibility with PeerTube
1061 Unfortunately, we don't have enough resources to provide hook compatibility between minor releases of PeerTube (for example between `1.2.x` and `1.3.x`).
1063 * Don't make assumptions and check every parameter you want to use. For example:
1067 target: 'filter:api.video.get.result',
1069 // We check the parameter exists and the name field exists too, to avoid exceptions
1070 if (video && video.name) video.name += ' <3'
1076 * Don't try to require parent PeerTube modules, only use `peertubeHelpers`. If you need another helper or a specific hook, please [create an issue](https://github.com/Chocobozzz/PeerTube/issues/new/choose)
1077 * Don't use PeerTube dependencies. Use your own :)
1079 If your plugin is broken with a new PeerTube release, update your code and the `peertubeEngine` field of your `package.json` field.
1080 This way, older PeerTube versions will still use your old plugin, and new PeerTube versions will use your updated plugin.
1082 ### Spam/moderation plugin
1084 If you want to create an antispam/moderation plugin, you could use the following hooks:
1085 * `filter:api.video.upload.accept.result`: to accept or not local uploads
1086 * `filter:api.video-thread.create.accept.result`: to accept or not local thread
1087 * `filter:api.video-comment-reply.create.accept.result`: to accept or not local replies
1088 * `filter:api.video-threads.list.result`: to change/hide the text of threads
1089 * `filter:api.video-thread-comments.list.result`: to change/hide the text of replies
1090 * `filter:video.auto-blacklist.result`: to automatically blacklist local or remote videos
1092 ### Other plugin examples
1094 You can take a look to "official" PeerTube plugins if you want to take inspiration from them: https://framagit.org/framasoft/peertube/official-plugins