Skip to main content
C plugins let you extend mpv with native code. They use the libmpv API but are loaded directly into the mpv process — you do not link against libmpv.so or libmpv.dll. Instead, your plugin uses symbols exported from the mpv host binary.
C plugins are enabled by default on Linux/BSD when the compiler supports -rdynamic, and are always enabled on Windows.

Plugin location

mpv discovers C plugins automatically from the scripts directory in its config directory (the same location as Lua scripts):
PlatformFile extensionExample path
Linux / BSD.so~/.config/mpv/scripts/myplugin.so
Windows.dll%APPDATA%\mpv\scripts\myplugin.dll
You can also load a plugin explicitly regardless of location:
mpv --script=/path/to/myplugin.so myvideo.mkv

Required export

Every C plugin must export exactly one function:
int mpv_open_cplugin(mpv_handle *handle);
mpv calls this function when the plugin is loaded. The function receives an mpv_handle that belongs to the plugin — it was created internally with the equivalent of mpv_create_client(). The function must not return for as long as the plugin is loaded. It runs in its own dedicated thread. When the function returns, the handle is destroyed.

Return values

Return valueMeaning
0Plugin loaded successfully
-1Error; mpv prints a loading failure message
Any other return value is reserved and triggers undefined behavior.

Event loop pattern

Because mpv_open_cplugin must not return, the typical plugin body is an event loop using mpv_wait_event():
#include <mpv/client.h>

int mpv_open_cplugin(mpv_handle *handle) {
    // Optional: install hooks, observe properties, etc.
    // mpv may block until mpv_wait_event() is called for the first time.
    // Use this window to set up hooks before playback begins.

    while (1) {
        mpv_event *event = mpv_wait_event(handle, -1);

        if (event->event_id == MPV_EVENT_SHUTDOWN)
            break;

        if (event->event_id == MPV_EVENT_FILE_LOADED) {
            char *path = mpv_get_property_string(handle, "path");
            // ... do something with path ...
            mpv_free(path);
        }
    }

    return 0; // 0 = success; handle is freed by mpv after this returns
}
Do not call mpv_destroy() or mpv_terminate_destroy() on the handle passed to mpv_open_cplugin. mpv owns and manages that handle’s lifetime.

Interacting with the player

Within mpv_open_cplugin, you have full access to the libmpv API through the provided handle:
// Send a command
const char *cmd[] = {"show-text", "Hello from C plugin!", "3000", NULL};
mpv_command(handle, cmd);

// Set a property
mpv_set_property_string(handle, "pause", "yes");

// Observe a property for changes
mpv_observe_property(handle, 1, "time-pos", MPV_FORMAT_DOUBLE);

// Register a hook to run code before a file loads
mpv_hook_add(handle, 0, "on_load", 0);

Compile instructions

Compile the plugin as a shared library. Do not link against libmpv.
gcc -shared -fPIC -o myplugin.so myplugin.c
The plugin resolves mpv symbols from the host binary at runtime via -rdynamic (which mpv enables when building).

Minimal complete example

#define MPV_CPLUGIN_DYNAMIC_SYM  // remove this line on Linux/BSD
#include <mpv/client.h>
#include <stdio.h>

int mpv_open_cplugin(mpv_handle *handle) {
    printf("C plugin loaded, client name: %s\n", mpv_client_name(handle));

    mpv_observe_property(handle, 1, "filename", MPV_FORMAT_STRING);

    while (1) {
        mpv_event *event = mpv_wait_event(handle, -1);

        switch (event->event_id) {
        case MPV_EVENT_PROPERTY_CHANGE: {
            mpv_event_property *prop = event->data;
            if (prop->format == MPV_FORMAT_STRING) {
                char *filename = *(char **)prop->data;
                printf("Now playing: %s\n", filename);
            }
            break;
        }
        case MPV_EVENT_SHUTDOWN:
            return 0;
        default:
            break;
        }
    }
}

Linkage rules

When this macro is defined, the mpv/client.h header declares a function pointer (e.g. pfn_mpv_wait_event) for every exported function, then #defines the original name to that pointer. mpv initializes all pointers before calling mpv_open_cplugin, so your code uses the pointers transparently. The pointers are decorated with __declspec(selectany) so multiple translation units do not cause linker errors.
Yes. render.h, render_gl.h, and stream_cb.h all support MPV_CPLUGIN_DYNAMIC_SYM and can be used from a C plugin. Include them after defining the macro.

Further examples

The mpv-examples repository contains additional C plugin examples covering hooks, IPC patterns, and property observation.

Build docs developers (and LLMs) love