# How to Build a Custom Properties UI with obs_properties_t for OBS Studio Source Configuration

> Learn to build custom properties UI for OBS Studio sources using obs_properties_t. Dynamically configure checkboxes, text fields, and more without custom UI code.

- Repository: [OBS Project/obs-studio](https://github.com/obsproject/obs-studio)
- Tags: how-to-guide
- Published: 2026-03-03

---

**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`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-properties.h) allows plugins to define configuration interfaces programmatically, enabling the Qt-based frontend in [`shared/properties-view/properties-view.cpp`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-properties.h). The frontend reads this container and instantiates corresponding Qt widgets in [`shared/properties-view/properties-view.cpp`](https://github.com/obsproject/obs-studio/blob/main/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 in [`libobs/obs-properties.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-properties.h), this opaque container holds a linked list of `obs_property_t` objects and optional user data (`param`). The `obs_properties_create()` function allocates this container (implemented in [`libobs/obs-properties.c`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-properties.c) at 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 via `obs_property_set_modified_callback` at lines 392-401 of [`obs-properties.c`](https://github.com/obsproject/obs-studio/blob/main/obs-properties.c)).

- **`obs_source_info::get_properties`** – This function pointer in the source’s registration structure must return a populated `obs_properties_t` instance. 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`](https://github.com/obsproject/obs-studio/blob/main/plugins/win-wasapi/win-wasapi.cpp), illustrates the standard workflow:

```cpp
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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/shared/properties-view/properties-view.cpp) through the `CreateWidget()` method:

```cpp
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:

```cpp
obs_properties_apply_settings(props, settings);

```

This function (lines 776-795 and 887-894 in [`libobs/obs-properties.c`](https://github.com/obsproject/obs-studio/blob/main/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`:

```cpp
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 a `QCheckBox`
- **Int / Float** – `obs_properties_add_int()` or `obs_properties_add_float()` create spin boxes; adding the `_slider` suffix sets `obs_number_type` to `OBS_NUMBER_SLIDER` for range sliders
- **Text** – `obs_properties_add_text()` generates `QLineEdit` or `QPlainTextEdit` depending on the `obs_text_type` parameter (default, password, or multi-line)
- **Path** – `obs_properties_add_path()` combines a text field with a browse button; `obs_path_type` distinguishes files from directories
- **List** – `obs_properties_add_list()` creates `QComboBox` widgets supporting string, integer, float, or boolean values; `OBS_COMBO_TYPE_EDITABLE` enables 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 nested `obs_properties_t` containers in collapsible `QGroupBox` layouts
- **Button** – `obs_properties_add_button()` creates `QPushButton` widgets 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:

```cpp
/* 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_t` without importing Qt headers
- **Core API** – Use `obs_properties_create()` and `obs_properties_add_*` functions from [`libobs/obs-properties.h`](https://github.com/obsproject/obs-studio/blob/main/libobs/obs-properties.h) to define widgets
- **Frontend rendering** – The Qt interface in [`shared/properties-view/properties-view.cpp`](https://github.com/obsproject/obs-studio/blob/main/shared/properties-view/properties-view.cpp) translates property types into native widgets automatically
- **Dynamic updates** – Implement `obs_property_set_modified_callback()` to react to user changes via `obs_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`](https://github.com/obsproject/obs-studio/blob/main/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`](https://github.com/obsproject/obs-studio/blob/main/properties-view.cpp) only responds to user interaction events.