How to Build a Custom Properties UI with obs_properties_t for OBS Studio Source Configuration
OBS Studio sources configure their UI dynamically by implementing a get_properties callback that returns an obs_properties_t object describing widgets like checkboxes, text fields, and dropdowns, which the Qt frontend renders automatically without requiring UI code in the source itself.
OBS Studio separates source logic from interface presentation through a declarative properties system. The obs_properties_t API in libobs/obs-properties.h allows plugins to define configuration interfaces programmatically, enabling the Qt-based frontend in shared/properties-view/properties-view.cpp to generate appropriate widgets. This architecture allows developers to build complex source configuration panels—including device selectors, sliders, and grouped sections—without writing any Qt code.
Architecture Overview: The Bridge Between libobs and Qt
The OBS property system acts as a platform-agnostic bridge between the core library (libobs) and the Qt-based configuration UI. A source implements a get_properties (or get_properties2) callback within its obs_source_info structure to expose configurable parameters. Inside this callback, the source constructs an obs_properties_t container using helper functions defined in libobs/obs-properties.h. The frontend reads this container and instantiates corresponding Qt widgets in shared/properties-view/properties-view.cpp, handling all user interaction and layout automatically.
When a user modifies a widget, the frontend writes the new value into an obs_data_t settings object and invokes obs_properties_apply_settings() to trigger any modified callbacks registered by the source. This design keeps source implementations completely free of UI dependencies while supporting dynamic, reactive configuration panels.
Core Data Structures and Callbacks
Understanding three primary structures is essential for implementing custom properties:
-
obs_properties_t– Defined inlibobs/obs-properties.h, this opaque container holds a linked list ofobs_property_tobjects and optional user data (param). Theobs_properties_create()function allocates this container (implemented inlibobs/obs-properties.cat lines 7-11). -
obs_property_t– Represents a single UI element such as a boolean checkbox, integer slider, or dropdown list. Each property stores type-specific data and optional modified callbacks (set viaobs_property_set_modified_callbackat lines 392-401 ofobs-properties.c). -
obs_source_info::get_properties– This function pointer in the source’s registration structure must return a populatedobs_properties_tinstance. The frontend calls this whenever it needs to display or refresh the source’s configuration panel.
Property name collisions are prevented internally by has_prop() (lines 889-902), which scans existing entries before new_prop() (lines 436-452) allocates and initializes new property objects.
Building Property Lists with obs_properties_add_*
Sources construct their UI declaratively by chaining helper functions. The following pattern, derived from the WASAPI audio source implementation in plugins/win-wasapi/win-wasapi.cpp, illustrates the standard workflow:
static obs_properties_t *GetWASAPIPropertiesInput(void *)
{
// 1️⃣ Create a fresh properties container
obs_properties_t *props = obs_properties_create();
// 2️⃣ Add a combo-list of audio devices
obs_property_t *device_prop = obs_properties_add_list(
props, OPT_DEVICE_ID,
obs_module_text("Device"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
// Populate the list (pseudo-code)
for (auto &dev : EnumerateWASAPIDevices())
obs_property_list_add_string(device_prop, dev.name, dev.id);
// 3️⃣ Add a simple boolean
obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING,
obs_module_text("UseDeviceTiming"));
// 4️⃣ Attach a modified-callback to react to user changes
obs_property_set_modified_callback(device_prop, UpdateWASAPIMethod);
return props; // 5️⃣ Return the fully-populated object
}
The obs_properties_add_list() function creates a property capable of holding multiple entries, which are appended using obs_property_list_add_string() (implementation at lines 1114-1172 of obs-properties.c). Modified callbacks enable sources to react to configuration changes dynamically—for example, repopulating dependent lists or reopening hardware devices when a selection changes.
UI Rendering in properties-view.cpp
The obs_properties_t object contains no platform-specific code. Instead, the frontend interprets the property list in shared/properties-view/properties-view.cpp through the CreateWidget() method:
QWidget *PropertiesView::CreateWidget(obs_property_t *property)
{
switch (obs_property_get_type(property)) {
case OBS_PROPERTY_BOOL:
return new QCheckBox(obs_property_description(property));
case OBS_PROPERTY_TEXT:
return new QLineEdit();
case OBS_PROPERTY_LIST:
// Build a QComboBox and fill it using the list API
return CreateComboBox(property);
/* … many more cases … */
}
}
The function queries the property type via obs_property_get_type() (line 788) to determine which Qt widget to instantiate. For every supported type, it creates the corresponding widget, sets the description as a label, and connects the widget’s changed signal to obs_properties_apply_settings(). Group properties (OBS_PROPERTY_GROUP) trigger recursive calls to create nested layouts, enabling collapsible sections without additional source code complexity.
Handling User Changes with Modified Callbacks
When a user edits a widget, the UI writes the new value into the source’s obs_data_t settings and immediately calls:
obs_properties_apply_settings(props, settings);
This function (lines 776-795 and 887-894 in libobs/obs-properties.c) walks the property list, identifies which entries have modified or modified2 callbacks attached, and invokes them in sequence. The callback signature receives the parent obs_properties_t, the specific obs_property_t that changed, and the updated obs_data_t:
bool my_modified_callback(obs_properties_t *props,
obs_property_t *p,
obs_data_t *settings);
Returning true from this callback signals that the UI should refresh other properties, enabling dynamic visibility updates or dependent list repopulation.
Property Types and Widget Mapping
The OBS properties API supports extensive UI primitives through specific helper functions:
- Bool –
obs_properties_add_bool()renders as aQCheckBox - Int / Float –
obs_properties_add_int()orobs_properties_add_float()create spin boxes; adding the_slidersuffix setsobs_number_typetoOBS_NUMBER_SLIDERfor range sliders - Text –
obs_properties_add_text()generatesQLineEditorQPlainTextEditdepending on theobs_text_typeparameter (default, password, or multi-line) - Path –
obs_properties_add_path()combines a text field with a browse button;obs_path_typedistinguishes files from directories - List –
obs_properties_add_list()createsQComboBoxwidgets supporting string, integer, float, or boolean values;OBS_COMBO_TYPE_EDITABLEenables user-defined string input - Editable List –
obs_properties_add_editable_list()provides custom list widgets with add/remove buttons for managing filter chains or playlists - Frame Rate –
obs_properties_add_frame_rate()presents combo boxes with FPS range selectors and named options - Group –
obs_properties_add_group()wraps nestedobs_properties_tcontainers in collapsibleQGroupBoxlayouts - Button –
obs_properties_add_button()createsQPushButtonwidgets that invoke callbacks when clicked
Complete Implementation Example
The following minimal source implementation demonstrates registering three properties—a boolean, a text field, and a dynamic device list—plus a modified callback that logs changes:
/* my-source.cpp ---------------------------------------------------------- */
#include <obs-module.h>
#include <util/platform.h>
#define SETTING_ENABLE "enable"
#define SETTING_NAME "name"
#define SETTING_DEVICE "device"
static const char *my_source_get_name(void *) { return "My Sample Source"; }
static void *my_source_create(obs_data_t *, obs_source_t *) { return nullptr; }
static void my_source_destroy(void *) {}
/* Modified callback – called after the user changes the device */
static bool device_changed(obs_properties_t *props,
obs_property_t *p,
obs_data_t *settings)
{
const char *new_dev = obs_data_get_string(settings, SETTING_DEVICE);
blog(LOG_INFO, "Device changed to %s", new_dev);
/* React (re-open device, etc.) */
return true;
}
/* Build the UI description */
static obs_properties_t *my_source_get_properties(void *)
{
obs_properties_t *props = obs_properties_create();
/* 1️⃣ Bool */
obs_properties_add_bool(props, SETTING_ENABLE,
obs_module_text("Enable"));
/* 2️⃣ Text */
obs_properties_add_text(props, SETTING_NAME,
obs_module_text("SourceName"),
OBS_TEXT_DEFAULT);
/* 3️⃣ Device list */
obs_property_t *dev = obs_properties_add_list(props, SETTING_DEVICE,
obs_module_text("Device"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
/* Populate demo list – in a real plug-in you’d enumerate hardware */
obs_property_list_add_string(dev, "Default", "default");
obs_property_list_add_string(dev, "Virtual 1", "virt1");
obs_property_list_add_string(dev, "Virtual 2", "virt2");
/* Attach callback */
obs_property_set_modified_callback(dev, device_changed);
return props;
}
/* Register the source */
static struct obs_source_info my_source_info = {
.id = "my_sample_source",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = 0,
.get_name = my_source_get_name,
.create = my_source_create,
.destroy = my_source_destroy,
.get_properties = my_source_get_properties,
.flags = OBS_SOURCE_CUSTOM,
};
bool obs_module_load(void)
{
obs_register_source(&my_source_info);
return true;
}
Lines 24-33 create the obs_properties_t container. Lines 35-48 add a checkbox, text field, and dropdown list. Line 46 registers device_changed as the modified callback, which the UI invokes whenever the user selects a new device entry.
Summary
- Separation of concerns – Sources describe UI elements via
obs_properties_twithout importing Qt headers - Core API – Use
obs_properties_create()andobs_properties_add_*functions fromlibobs/obs-properties.hto define widgets - Frontend rendering – The Qt interface in
shared/properties-view/properties-view.cpptranslates property types into native widgets automatically - Dynamic updates – Implement
obs_property_set_modified_callback()to react to user changes viaobs_properties_apply_settings() - Type coverage – The API supports booleans, numbers, text, paths, lists, editable lists, frame rates, groups, and buttons
Frequently Asked Questions
How do I make a property visible only when another checkbox is enabled?
Use obs_property_set_modified_callback() on the controlling boolean. Inside the callback, call obs_property_set_visible() on dependent properties based on the current value in obs_data_t, then return true to trigger a UI refresh. The plugins/obs-vst/obs-vst.cpp implementation demonstrates this pattern for showing advanced options only when enabled.
What is the difference between get_properties and get_properties2 in obs_source_info?
get_properties receives only the source’s internal data pointer, while get_properties2 receives both the data pointer and the obs_source_t instance itself. The latter allows callbacks to access runtime source state when building the property list, useful for sources that need to query current hardware status or frame dimensions during UI construction.
Can I add tooltips or descriptive text to individual properties?
Yes. Call obs_property_set_long_description() on any obs_property_t after creation. This stores help text that the Qt frontend displays as tooltips or descriptive labels alongside the widget, improving usability for complex configuration options.
Why are my modified callbacks not firing when I change values programmatically?
obs_properties_apply_settings() only invokes modified callbacks when called explicitly. If you modify obs_data_t programmatically without going through the UI, you must manually call obs_properties_apply_settings(props, settings) to trigger callbacks, as the automated UI signal connection in properties-view.cpp only responds to user interaction events.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →