Riff is built the way a feature plugin is: one track shape everywhere, read through WordPress's own APIs. Everything here is public and keeps working within a major version; anything that has to go is deprecated for a release first.
Hooks
Every filter and action has a page of its own, generated from the docblock above the line it fires on. All hooks, PHP and JavaScript, are searchable in one list.
Filters
| Filter | Description |
|---|---|
| riff_abilities | Filters the abilities Riff registers, so an extension adds its own the way Riff adds its: each the REST route it runs, with what it takes and what it answers. Each is registered in Riff's category, run as a REST request to its route and allowed what the route's own permission callback allows, and offered to an MCP client only where the site turns that on. |
| riff_about_sections | Filters the sections of the About panel, Riff's own included: track, the cover, the title and the artist and album, and credits, what the file's tags say about who made it. |
| riff_add_music_url | Filters where the Listen app sends someone who may upload to add music: the account menu's Add music, and Add your first album in an empty library. |
| riff_app_at_root | Filters whether the site's own address opens the Listen page's app. On, a request for the home page with no other query is the Listen page in standalone mode, every link to the app and the manifest's start address are the home page, and the app/ address redirects there. The Listen page's own address and the theme's front page setting are left as they are. |
| riff_app_manifest | Filters the Listen app's web app manifest, for a host that names, colours or gives icons to each site's app its own way. |
| riff_app_precache | Filters what the installed Listen app's service worker keeps on the device, so it is there offline and on every launch after the first: a host's fonts that the first paint doesn't show, say. The fonts riff_app_preload_fonts returns are already in it. |
| riff_app_preload_fonts | Filters the fonts a Listen page preloads, the faces its first paint shows, so a host that draws the app in fonts of its own has them fetched with the page rather than after the stylesheet asks. The installed app's service worker keeps them on the device too. |
| riff_app_views | Filters the Listen app's views: Riff's own, by slug (home, search, albums, artists, tracks, posts and library), and any a plugin adds. |
| riff_can_play | Filters whether the current visitor may play a track. |
| riff_client_navigation | Filters whether a link on the site is followed without leaving the page, so what plays carries on. Only a block theme can: its template is the region that is swapped. |
| riff_cover_accent | Filters whether the player takes its accent from the cover of the track playing: the cover's hue, muted and lightened until it reads on the player's dark surfaces, as --riff-cover-accent, on the seek bar, the playing bars, the current Up next item and a wash behind Now Playing, and in the Listen app's own document on its links, the track playing, Liked songs and the Browse tiles. A cover that is grey, near black or near white gives none, and the accent is the site's --riff-accent, as it is everywhere with this off. |
| riff_enabled | Filters whether Riff's player loads on the current front-end request. |
| riff_extensions | Filters the extensions Riff loads, by name: a host can leave one out, or name another plugin it has placed in Riff's extensions/ folder. |
| riff_mcp | Filters whether AI assistants may use Riff's abilities over MCP. On, and with the MCP Adapter plugin active, /wp-json/mcp/riff serves each of them as a tool to a signed-in user, and the adapter's default server lists them. Each still does only what that user may do. Off, there is no such server, and the default server does not list them. |
| riff_post_blocks | Filters whether a post's blocks are served as a tree at riff/v1/posts/{id}/blocks to someone who cannot edit the post. |
| riff_profile_url | Filters where the Listen app's account menu sends a listener to edit their profile, for a host or a membership plugin with a profile page of its own. |
| riff_protect_storage | Filters whether the storage a file is kept in elsewhere was made private, when it is protected, or public again, when it isn't. A plugin that keeps uploads in object storage or on a CDN answers for its own files: true once the copy there is private (or public again), or a WP_Error to say why it couldn't be, which the Media Library shows. Without an answer, a file only on this server is moved into the protected folder, and one kept elsewhere is not protected, as moving it would leave it public there. Riff can't send a file it doesn't have, so a plugin that protects its files signs their addresses through riff_protected_url too. |
| riff_protected_path | Filters the folder protected files are kept in, so the web server never has them to send: an absolute path outside the web root, which Riff makes when it is first needed, keeping each site's files in a folder of its own inside it (the site's ID on a network). Empty, the default, keeps them in a folder under uploads, which Apache is told to refuse and Nginx needs a rule for. |
| riff_protected_send | Filters how a protected file is handed over: 'php' sends it from PHP; 'x-sendfile' asks Apache's mod_xsendfile or LiteSpeed to send it; 'x-accel-redirect' asks Nginx, which then needs an internal location for the protected folder (see the readme). |
| riff_protected_url | Filters the address a protected file is sent from instead of WordPress: a signed address on a CDN or in object storage, such as a CloudFront or S3 presigned URL or a Bunny or Cloudflare token, that stops working at $expires. Riff has checked that the visitor may have the file before it asks, and sends them on with a 302 nothing caches. Empty, the default, sends the file as riff_protected_send says. |
| riff_sources | Filters the sources tracks are read from, keyed by id. |
| riff_together_share | Filters whether a listener who hasn't chosen shares what they play with the site's other listeners. A site where listeners shouldn't see each other unless they ask, a class of children say, returns false. |
| riff_track | Filters a track before the player reads it. |
| riff_turntable | Filters whether Now Playing shows the record and the turntable. Off, it shows the cover on its own, and a touch screen on its side shows the cover beside the controls. |
| riff_unlock_url | Filters where a locked track sends the visitor to unlock it: a sign-up, membership or checkout page. |
Actions
| Action | Description |
|---|---|
| riff_before_player | Fires before the player's markup. |
| riff_below_turntable | Fires in the place under the turntable, for what a plugin shows beside the record. Print one element for each thing: the place takes room only while one that isn't hidden is in it, and only where the turntable is the view. The record is out when #riff has data-riff-deck="out". Script it from there; window.riff.analyser() hears the music. |
| riff_private_refused | Fires before a private site refuses a visitor, for a page of the site's own in place of WordPress's message. |
| riff_register | Fires once a request, before the player's controls and views are first read: register them here with riff_register_control() and riff_register_view(), or take one of Riff's own away. A plugin's keyboard shortcuts are registered here too, with riff_register_shortcut(). |
Functions
The bar's controls and Now Playing's views are registered on the server, as WordPress registers blocks, so the page has every button and every view before any script runs. Register them on the riff_register action; Riff registers its own there too, and any of them can be taken away. In the browser, window.riff.view() and window.riff.control() give each what it does, by its id. How the player works has the whole of it.
add_action( 'riff_register', function () {
riff_register_view( 'credits', array(
'label' => __( 'Credits', 'my-plugin' ),
'icon' => '<svg viewBox="0 0 24 24">…</svg>',
'order' => 32,
) );
} );riff_register_control( string $id, array $args = array() ): bool
Registers a control in the player's bar. Call it on the riff_register action.
Parameters
$idstring- Lowercase letters, digits and dashes.
$argsarray- The control.
labelstringWhat it is called, for a screen reader and a pointer's tooltip.iconstringAn icon of Riff's own by name, or SVG markup.orderintWhere it sits among the others, lowest first. Default 50.wideboolOnly on a wide screen; a phone's bar keeps to the track, Play and what it needs. Default false.rendercallableMarkup of its own in place of a button, for an element that is not one; it is given the id and returns one element.scriptstringThe script module that brings it to life, which Riff enqueues wherever the player is. Register the module yourself.
Return
bool Whether it was registered.
Since 5.8.0plugins/riff/src/functions.phpView on GitHub
riff_unregister_control( string $id ): bool
Takes a control away, Riff's own included.
Parameters
$idstring- The control's id.
Return
bool Whether there was one.
Since 5.8.0plugins/riff/src/functions.phpView on GitHub
riff_register_view( string $id, array $args = array() ): bool
Registers a view the listener opens from its button in the bar: a screen of Now Playing, or a panel beside the page. Call it on the riff_register action.
Parameters
$idstring- Lowercase letters, digits and dashes.
$argsarray- The view.
labelstringIts name: its button's, its chip's and its region's.iconstringAn icon of Riff's own by name, or SVG markup.orderintWhere its button sits among the bar's controls, lowest first. Default 50.placestringscreento take the whole screen above the bar, as Now Playing does;sidefor a panel beside the page. Defaultscreen.stageboolFor a screen that is Now Playing's own stage, the track's art or its record, rather than a region of its own, with no button in the bar, which opens it itself. Default false.wideboolIts button only on a wide screen; on a phone its chip in Now Playing opens it. Default true.rendercallableMarkup for its region as the page is drawn; the script fills it as it opens. Given the id.scriptstringThe script module that brings it to life, which Riff enqueues wherever the player is. Register the module yourself.
Return
bool Whether it was registered.
Since 5.8.0plugins/riff/src/functions.phpView on GitHub
riff_unregister_view( string $id ): bool
Takes a view away, Riff's own included.
Parameters
$idstring- The view's id.
Return
bool Whether there was one.
Since 5.8.0plugins/riff/src/functions.phpView on GitHub
riff_register_shortcut( string $id, array $args = array() ): bool
Registers a key the player acts on for a plugin. It follows Riff's own single-key shortcuts: it does nothing while the listener types, it is listed with them on the listener's profile, and it is off wherever they are off. Call it on the riff_register action, and give it what it does with window.riff.shortcut().
Parameters
$idstring- Lowercase letters, digits and dashes.
$argsarray- The shortcut: its
keyand itslabel.keystringOne character, as the key types it: a letter, which acts without Shift whatever its case, a digit, or a mark such as[or+. Not a space, and not one of Riff's own letters, K, J, L, N and P, nor a key another shortcut has.labelstringWhat it does, as the profile lists it beside the key.
Return
bool Whether it was registered.
Since 5.9.0plugins/riff/src/functions.phpView on GitHub
riff_listen_page(): int
The site's Listen page, where the player opens as an app: the oldest published page holding the Riff block or using the Listen template.
Return
int Page ID, or 0 when there is none.
Since 6.5.0plugins/riff/src/functions.phpView on GitHub
Sources
A source answers three questions: which posts it can read, what tracks a post holds, and how those tracks render as blocks the player takes over. The library asks every source; the first that claims a post reads it.
A plugin adds its own post type or catalogue by implementing Riff\Source and registering it on init; the library, the REST routes and the player then read it like any other.
add_action( 'init', function () {
Riff\Sources::register( new My_Catalogue_Source() );
} );The Riff\Source interface
public function id(): string
A short id, such as "blocks".
Return
string
Since 4.0.0plugins/riff/src/interface-source.phpView on GitHub
public function supports( WP_Post $post ): bool
Whether this source can read the post.
Parameters
$postWP_Post- Post.
Return
bool
Since 4.0.0plugins/riff/src/interface-source.phpView on GitHub
public function tracks( WP_Post $post ): array
The tracks in the post, in order, in the shape Tracks::schema() describes.
Parameters
$postWP_Post- Post.
Return
array[]
Since 4.0.0plugins/riff/src/interface-source.phpView on GitHub
public function html( WP_Post $post ): string
The tracks rendered as figures carrying data-riff-track, which riff.js turns into rows.
Parameters
$postWP_Post- Post.
Return
string
Since 4.0.0plugins/riff/src/interface-source.phpView on GitHub
public function where(): string
A WHERE clause fragment (without a leading AND) matching posts this source can read, or an empty string when the source cannot say from SQL alone.
Return
string
Since 4.0.0plugins/riff/src/interface-source.phpView on GitHub
Riff\Sources::register( Source $source ): void
Registers a source. The core blocks source registers itself at boot; a plugin registers its own on init.
Parameters
$sourceSource- Source.
Since 4.0.0plugins/riff/src/class-sources.phpView on GitHub
Riff\Sources::unregister( string $id ): void
Removes a source.
Parameters
$idstring- Source id.
Since 4.0.0plugins/riff/src/class-sources.phpView on GitHub
Block Bindings
riff/track (“Riff track”) is a Block Bindings source: a Heading or Paragraph bound to it shows a track's details, and an Image bound to it shows the cover, with no Riff block on the page. The binding's key picks the detail and id names the attachment; with no id, it is the post's first track. Every value is the track as the track schema has it, after riff_track, and a named track shows only where the media route would show it. In the editor the block shows that it is bound; the value is filled on the front end.
<!-- wp:heading {"metadata":{"bindings":{"content":{"source":"riff/track","args":{"key":"title","id":123}}}}} -->
<h2 class="wp-block-heading"></h2>
<!-- /wp:heading -->
<!-- wp:image {"metadata":{"bindings":{"url":{"source":"riff/track","args":{"key":"cover","id":123}}}}} -->
<figure class="wp-block-image"><img src="" alt=""/></figure>
<!-- /wp:image -->| Key | Shows |
|---|---|
title | The track’s title. |
artist | The artist. |
album | The album. |
year | The year, or nothing when the file has none. |
duration | The length as the player shows it, such as 4:32, or nothing when unknown. |
quality | The format and rate as the player’s badge shows them, such as MP3 · 128 kbps or FLAC · 24-bit 96 kHz. |
cover | The address of the largest artwork: the file’s own, else its post’s, else the cover Riff draws. For an Image block’s url. |
Abilities
Through the Abilities API, AI tools and other plugins list the library and keep a listener's likes and playlists. Each ability runs its riff/v1 route as a REST request, so it answers what the route answers and is allowed what the route allows; the ones about likes, history and playlists need a signed-in user. Nothing plays through an ability: playback happens in a browser.
$albums = wp_get_ability( 'riff/albums' )->execute( array( 'search' => 'Compositions' ) );| Ability | What it does | Input | |
|---|---|---|---|
riff/tracks | Lists the tracks the requester can see, searchable by title, artist or album, or those on one album or by one artist, or named by ID, a page at a time. | search: Words to search for.album: Only the tracks on this album, by term ID, in album order.artist: Only the tracks by this artist, by term ID.include: Only these, by ID, such as one to look at on its own.orderby: By title, or the most recently added first.page: Page of results, from 1.per_page: Tracks per page, up to 100. | Read only |
riff/albums | Lists the albums with a track the requester can see, by name, each with its artist, artwork and track count, searchable by name. An album's tracks are riff/tracks with its ID as album. | search: Words to search for.artist: Only the albums with a track by this artist, by term ID.include: Only these, by ID, such as one to look at on its own.page: Page of results, from 1. | Read only |
riff/artists | Lists the artists with a track the requester can see, by name, each with a track count, searchable by name. An artist's tracks are riff/tracks with their ID as artist. | search: Words to search for.include: Only these, by ID, such as one to look at on its own.page: Page of results, from 1. | Read only |
riff/library | Lists the posts with music the requester can see, newest first, each with its title, artwork, track count and length, a page at a time. | page: Page of results, from 1. | Read only |
riff/library-post | Gets one published post with music, such as an album, with its tracks in order as the player reads them. | id (required): The post's ID, as riff/library lists it. | Read only |
riff/library-summary | Counts the tracks, albums, artists and posts with music, and says how much space the audio and every upload take, and the site's limit where it has one. For someone who can upload files. | – | Read only |
riff/likes | Lists what the signed-in user has liked: tracks, albums and artists, newest first. | – | Read only |
riff/history | Lists the tracks the signed-in user has played, most recent first. | – | Read only |
riff/playlists | Lists the signed-in user's playlists, each with its tracks. | – | Read only |
riff/like | Adds a track, album or artist to the signed-in user's likes. | kind (required): What to like: a track, an album or an artist.id (required): Its ID: an audio attachment, an album term or an artist term. | Writes |
riff/unlike | Takes a track, album or artist out of the signed-in user's likes. | kind (required): What to like: a track, an album or an artist.id (required): Its ID: an audio attachment, an album term or an artist term. | Writes |
riff/create-playlist | Makes a playlist for the signed-in user, empty or with tracks. | title (required): The playlist's name.tracks: Audio attachment IDs, in order. | Writes |
riff/add-to-playlist | Adds tracks, by audio attachment ID, to the end of one of the signed-in user's playlists. | playlist (required): The playlist's ID.tracks (required): Audio attachment IDs, in order. | Writes |
riff/remove-from-playlist | Takes the track at a place in one of the signed-in user's playlists out, counting from 0. | playlist (required): The playlist's ID.index (required): The track's place in the playlist, from 0. | Writes |
riff/create-album | Makes an album of audio files already in the Media Library: a post with one audio block for each, in the order given, a draft unless asked otherwise. For someone who can write posts and edit the files. | title (required): The album's title.tracks (required): Audio attachment IDs, in order.status: Draft, for its author to look over first; published; or private. Draft unless given.artwork: An image attachment ID for the cover; the first track's artwork when not given. | Writes |
riff/update-track | Sets a track's title, artist, album or artwork, through the Media Library's own REST route. For someone who can edit the file. | id (required): The audio attachment's ID.title: The track's title.artist: The artist, kept with the file's tags; empty to clear it.album: The album, kept with the file's tags; empty to clear it.artwork: An image attachment ID for the track's artwork; 0 to take it away. | Writes |
riff/import-zip | Imports a zip of an album, or of a folder of albums, already uploaded to the Media Library and attached to no post, as uploading it there does: in the background, into private album posts with each track Protected, for its uploader to publish. For someone who can write posts and edit the zip. | attachment (required): The zip's attachment ID. | Writes |
The Listen block
riff/app: Your music library and the player, on this page. Every post with audio or video is listed, and the music keeps playing while you browse. One per page. It supports align, color, typography, spacing, __experimentalBorder, shadow.
<!-- wp:riff/app {"align":"full"} /-->Anywhere blocks are not an option, the [riff] shortcode does the same.
[riff]
[riff], the same as the block.
Return
string
Since 4.0.0plugins/riff/features/listen/class-app.phpView on GitHub