diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors
index c9754bf7d83..c6cc45f3b87 100644
--- a/app/Kconfig.behaviors
+++ b/app/Kconfig.behaviors
@@ -1,6 +1,12 @@
 # Copyright (c) 2023 The ZMK Contributors
 # SPDX-License-Identifier: MIT
 
+config ZMK_BEHAVIOR_METADATA
+    bool "Metadata"
+    help
+      Enabling this option adds APIs for documenting and fetching
+      metadata describing a behaviors name, and supported parameters.
+
 config ZMK_BEHAVIOR_KEY_TOGGLE
     bool
     default y
@@ -35,4 +41,4 @@ config ZMK_BEHAVIOR_SENSOR_ROTATE_VAR
 config ZMK_BEHAVIOR_MACRO
     bool
     default y
-    depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED
\ No newline at end of file
+    depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED
diff --git a/app/dts/behaviors/backlight.dtsi b/app/dts/behaviors/backlight.dtsi
index 54c83ff44c1..dd045effed3 100644
--- a/app/dts/behaviors/backlight.dtsi
+++ b/app/dts/behaviors/backlight.dtsi
@@ -10,6 +10,7 @@
         /omit-if-no-ref/ bl: bcklight {
             compatible = "zmk,behavior-backlight";
             #binding-cells = <2>;
+            display-name = "Backlight";
         };
     };
 };
diff --git a/app/dts/behaviors/bluetooth.dtsi b/app/dts/behaviors/bluetooth.dtsi
index 40557b7a28c..bece156f8fb 100644
--- a/app/dts/behaviors/bluetooth.dtsi
+++ b/app/dts/behaviors/bluetooth.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ bt: bluetooth {
             compatible = "zmk,behavior-bluetooth";
             #binding-cells = <2>;
+            display-name = "Bluetooth";
         };
     };
 };
diff --git a/app/dts/behaviors/caps_word.dtsi b/app/dts/behaviors/caps_word.dtsi
index 795fbc08439..05431bd8db2 100644
--- a/app/dts/behaviors/caps_word.dtsi
+++ b/app/dts/behaviors/caps_word.dtsi
@@ -12,6 +12,7 @@
             compatible = "zmk,behavior-caps-word";
             #binding-cells = <0>;
             continue-list = <UNDERSCORE BACKSPACE DELETE>;
+            display-name = "Caps Word";
         };
     };
 };
diff --git a/app/dts/behaviors/ext_power.dtsi b/app/dts/behaviors/ext_power.dtsi
index 2ae1daf84a8..08113f94bbe 100644
--- a/app/dts/behaviors/ext_power.dtsi
+++ b/app/dts/behaviors/ext_power.dtsi
@@ -10,6 +10,7 @@
         ext_power: extpower {
             compatible = "zmk,behavior-ext-power";
             #binding-cells = <1>;
+            display-name = "External Power";
         };
     };
 };
diff --git a/app/dts/behaviors/gresc.dtsi b/app/dts/behaviors/gresc.dtsi
index 59a7329178f..2643a383d81 100644
--- a/app/dts/behaviors/gresc.dtsi
+++ b/app/dts/behaviors/gresc.dtsi
@@ -13,6 +13,7 @@
             #binding-cells = <0>;
             bindings = <&kp ESC>, <&kp GRAVE>;
             mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
+            display-name = "Grave/Escape";
         };
     };
 };
diff --git a/app/dts/behaviors/key_press.dtsi b/app/dts/behaviors/key_press.dtsi
index ddaf7eed374..2435699b6ab 100644
--- a/app/dts/behaviors/key_press.dtsi
+++ b/app/dts/behaviors/key_press.dtsi
@@ -10,6 +10,7 @@
         /omit-if-no-ref/ cp: kp: key_press {
             compatible = "zmk,behavior-key-press";
             #binding-cells = <1>;
+            display-name = "Key Press";
         };
     };
 };
diff --git a/app/dts/behaviors/key_repeat.dtsi b/app/dts/behaviors/key_repeat.dtsi
index 88910f6271c..cd5d3771dcb 100644
--- a/app/dts/behaviors/key_repeat.dtsi
+++ b/app/dts/behaviors/key_repeat.dtsi
@@ -12,6 +12,7 @@
             compatible = "zmk,behavior-key-repeat";
             #binding-cells = <0>;
             usage-pages = <HID_USAGE_KEY>;
+            display-name = "Key Repeat";
         };
     };
 };
diff --git a/app/dts/behaviors/key_toggle.dtsi b/app/dts/behaviors/key_toggle.dtsi
index a3e3f36f270..a7b66aab1af 100644
--- a/app/dts/behaviors/key_toggle.dtsi
+++ b/app/dts/behaviors/key_toggle.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ kt: key_toggle {
             compatible = "zmk,behavior-key-toggle";
             #binding-cells = <1>;
+            display-name = "Key Toggle";
         };
     };
 };
diff --git a/app/dts/behaviors/layer_tap.dtsi b/app/dts/behaviors/layer_tap.dtsi
index dc953e9358b..2858bf17bc5 100644
--- a/app/dts/behaviors/layer_tap.dtsi
+++ b/app/dts/behaviors/layer_tap.dtsi
@@ -12,6 +12,7 @@
             flavor = "tap-preferred";
             tapping-term-ms = <200>;
             bindings = <&mo>, <&kp>;
+            display-name = "Layer-Tap";
         };
     };
 };
diff --git a/app/dts/behaviors/mod_tap.dtsi b/app/dts/behaviors/mod_tap.dtsi
index 38bb34fe5c4..0b46f77e739 100644
--- a/app/dts/behaviors/mod_tap.dtsi
+++ b/app/dts/behaviors/mod_tap.dtsi
@@ -12,6 +12,7 @@
             flavor = "hold-preferred";
             tapping-term-ms = <200>;
             bindings = <&kp>, <&kp>;
+            display-name = "Mod-Tap";
         };
     };
 };
diff --git a/app/dts/behaviors/momentary_layer.dtsi b/app/dts/behaviors/momentary_layer.dtsi
index 6d85165dbb3..cae08d5f101 100644
--- a/app/dts/behaviors/momentary_layer.dtsi
+++ b/app/dts/behaviors/momentary_layer.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ mo: momentary_layer {
             compatible = "zmk,behavior-momentary-layer";
             #binding-cells = <1>;
+            display-name = "Momentary Layer";
         };
     };
 };
diff --git a/app/dts/behaviors/none.dtsi b/app/dts/behaviors/none.dtsi
index 13d056f0cf2..a9a820c30b5 100644
--- a/app/dts/behaviors/none.dtsi
+++ b/app/dts/behaviors/none.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ none: none {
             compatible = "zmk,behavior-none";
             #binding-cells = <0>;
+            display-name = "None";
         };
     };
 };
diff --git a/app/dts/behaviors/outputs.dtsi b/app/dts/behaviors/outputs.dtsi
index f7737196719..3047852adce 100644
--- a/app/dts/behaviors/outputs.dtsi
+++ b/app/dts/behaviors/outputs.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ out: outputs {
             compatible = "zmk,behavior-outputs";
             #binding-cells = <1>;
+            display-name = "Output Selection";
         };
     };
 };
diff --git a/app/dts/behaviors/reset.dtsi b/app/dts/behaviors/reset.dtsi
index e407b107b90..2aa41d7d6e8 100644
--- a/app/dts/behaviors/reset.dtsi
+++ b/app/dts/behaviors/reset.dtsi
@@ -12,6 +12,7 @@
         sys_reset: sysreset {
             compatible = "zmk,behavior-reset";
             #binding-cells = <0>;
+            display-name = "Reset";
         };
 
         // Behavior can be invoked on peripherals, so name must be <= 8 characters.
@@ -19,6 +20,7 @@
             compatible = "zmk,behavior-reset";
             type = <RST_UF2>;
             #binding-cells = <0>;
+            display-name = "Bootloader";
         };
     };
 };
diff --git a/app/dts/behaviors/rgb_underglow.dtsi b/app/dts/behaviors/rgb_underglow.dtsi
index 969518a6ff3..0764005872d 100644
--- a/app/dts/behaviors/rgb_underglow.dtsi
+++ b/app/dts/behaviors/rgb_underglow.dtsi
@@ -10,6 +10,7 @@
         rgb_ug: rgb_ug {
             compatible = "zmk,behavior-rgb-underglow";
             #binding-cells = <2>;
+            display-name = "Underglow";
         };
     };
 };
diff --git a/app/dts/behaviors/sticky_key.dtsi b/app/dts/behaviors/sticky_key.dtsi
index c8973d4df2c..382a7254e7b 100644
--- a/app/dts/behaviors/sticky_key.dtsi
+++ b/app/dts/behaviors/sticky_key.dtsi
@@ -12,6 +12,7 @@
             release-after-ms = <1000>;
             bindings = <&kp>;
             ignore-modifiers;
+            display-name = "Sticky Key";
         };
         /omit-if-no-ref/ sl: sticky_layer {
             compatible = "zmk,behavior-sticky-key";
@@ -19,6 +20,7 @@
             release-after-ms = <1000>;
             bindings = <&mo>;
             quick-release;
+            display-name = "Sticky Layer";
         };
     };
 
diff --git a/app/dts/behaviors/to_layer.dtsi b/app/dts/behaviors/to_layer.dtsi
index 904f023da53..3c740209cb1 100644
--- a/app/dts/behaviors/to_layer.dtsi
+++ b/app/dts/behaviors/to_layer.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ to: to_layer {
             compatible = "zmk,behavior-to-layer";
             #binding-cells = <1>;
+            display-name = "To Layer";
         };
     };
 };
diff --git a/app/dts/behaviors/toggle_layer.dtsi b/app/dts/behaviors/toggle_layer.dtsi
index 05f2988e08c..ea9b25b7c1d 100644
--- a/app/dts/behaviors/toggle_layer.dtsi
+++ b/app/dts/behaviors/toggle_layer.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ tog: toggle_layer {
             compatible = "zmk,behavior-toggle-layer";
             #binding-cells = <1>;
+            display-name = "Toggle Layer";
         };
     };
 };
diff --git a/app/dts/behaviors/transparent.dtsi b/app/dts/behaviors/transparent.dtsi
index 3586f02afab..03ec36a64e8 100644
--- a/app/dts/behaviors/transparent.dtsi
+++ b/app/dts/behaviors/transparent.dtsi
@@ -9,6 +9,7 @@
         /omit-if-no-ref/ trans: transparent {
             compatible = "zmk,behavior-transparent";
             #binding-cells = <0>;
+            display-name = "Transparent";
         };
     };
 };
diff --git a/app/dts/bindings/behaviors/behavior-metadata.yaml b/app/dts/bindings/behaviors/behavior-metadata.yaml
new file mode 100644
index 00000000000..3a758ba3e22
--- /dev/null
+++ b/app/dts/bindings/behaviors/behavior-metadata.yaml
@@ -0,0 +1,6 @@
+# Copyright (c) 2024 The ZMK Contributors
+# SPDX-License-Identifier: MIT
+
+properties:
+  display-name:
+    type: string
diff --git a/app/dts/bindings/behaviors/one_param.yaml b/app/dts/bindings/behaviors/one_param.yaml
index 9a503e8a815..fa4c2dc0b44 100644
--- a/app/dts/bindings/behaviors/one_param.yaml
+++ b/app/dts/bindings/behaviors/one_param.yaml
@@ -1,6 +1,8 @@
 # Copyright (c) 2020 The ZMK Contributors
 # SPDX-License-Identifier: MIT
 
+include: behavior-metadata.yaml
+
 properties:
   label:
     type: string
diff --git a/app/dts/bindings/behaviors/two_param.yaml b/app/dts/bindings/behaviors/two_param.yaml
index 4f342301bb3..af9618e1624 100644
--- a/app/dts/bindings/behaviors/two_param.yaml
+++ b/app/dts/bindings/behaviors/two_param.yaml
@@ -1,6 +1,8 @@
 # Copyright (c) 2020 The ZMK Contributors
 # SPDX-License-Identifier: MIT
 
+include: behavior-metadata.yaml
+
 properties:
   label:
     type: string
diff --git a/app/dts/bindings/behaviors/zero_param.yaml b/app/dts/bindings/behaviors/zero_param.yaml
index 79d0dcaed3f..deed5a1218b 100644
--- a/app/dts/bindings/behaviors/zero_param.yaml
+++ b/app/dts/bindings/behaviors/zero_param.yaml
@@ -1,6 +1,8 @@
 # Copyright (c) 2020 The ZMK Contributors
 # SPDX-License-Identifier: MIT
 
+include: behavior-metadata.yaml
+
 properties:
   label:
     type: string
diff --git a/app/include/drivers/behavior.h b/app/include/drivers/behavior.h
index 3936da5e4be..3dd6e0623f7 100644
--- a/app/include/drivers/behavior.h
+++ b/app/include/drivers/behavior.h
@@ -23,6 +23,39 @@
  * (Internal use only.)
  */
 
+struct behavior_parameter_value_metadata {
+    char *display_name;
+
+    union {
+        uint32_t value;
+        struct {
+            int32_t min;
+            int32_t max;
+        } range;
+    };
+
+    enum {
+        BEHAVIOR_PARAMETER_VALUE_TYPE_NIL = 0,
+        BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE = 1,
+        BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE = 2,
+        BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE = 3,
+        BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_INDEX = 4,
+    } type;
+};
+
+struct behavior_parameter_metadata_set {
+    size_t param1_values_len;
+    const struct behavior_parameter_value_metadata *param1_values;
+
+    size_t param2_values_len;
+    const struct behavior_parameter_value_metadata *param2_values;
+};
+
+struct behavior_parameter_metadata {
+    size_t sets_len;
+    const struct behavior_parameter_metadata_set *sets;
+};
+
 enum behavior_sensor_binding_process_mode {
     BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_TRIGGER,
     BEHAVIOR_SENSOR_BINDING_PROCESS_MODE_DISCARD,
@@ -37,6 +70,10 @@ typedef int (*behavior_sensor_keymap_binding_accept_data_callback_t)(
     struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event,
     const struct zmk_sensor_config *sensor_config, size_t channel_data_size,
     const struct zmk_sensor_channel_data channel_data[channel_data_size]);
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+typedef int (*behavior_get_parameter_metadata_t)(
+    const struct device *behavior, struct behavior_parameter_metadata *param_metadata);
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 
 enum behavior_locality {
     BEHAVIOR_LOCALITY_CENTRAL,
@@ -51,23 +88,54 @@ __subsystem struct behavior_driver_api {
     behavior_keymap_binding_callback_t binding_released;
     behavior_sensor_keymap_binding_accept_data_callback_t sensor_binding_accept_data;
     behavior_sensor_keymap_binding_process_callback_t sensor_binding_process;
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    behavior_get_parameter_metadata_t get_parameter_metadata;
+    const struct behavior_parameter_metadata *parameter_metadata;
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 /**
  * @endcond
  */
 
+struct zmk_behavior_metadata {
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    const char *display_name;
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+};
+
 struct zmk_behavior_ref {
     const struct device *device;
+    const struct zmk_behavior_metadata metadata;
 };
 
+#define ZMK_BEHAVIOR_REF_DT_NAME(node_id) _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id))
+
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+#define ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id)                                                 \
+    { .display_name = DT_PROP_OR(node_id, display_name, DEVICE_DT_NAME(node_id)), }
+
+#else
+
+#define ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id)                                                 \
+    {}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+#define ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev)                                                \
+    { .device = _dev, .metadata = ZMK_BEHAVIOR_METADATA_INITIALIZER(node_id), }
+
+#define ZMK_BEHAVIOR_REF_DEFINE(name, node_id, _dev)                                               \
+    static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref, name) =                                 \
+        ZMK_BEHAVIOR_REF_INITIALIZER(node_id, _dev)
+
+#define ZMK_BEHAVIOR_REF_DT_DEFINE(node_id)                                                        \
+    ZMK_BEHAVIOR_REF_DEFINE(ZMK_BEHAVIOR_REF_DT_NAME(node_id), node_id, DEVICE_DT_GET(node_id))
+
 /**
  * Registers @p node_id as a behavior.
  */
-#define BEHAVIOR_DEFINE(node_id)                                                                   \
-    static const STRUCT_SECTION_ITERABLE(zmk_behavior_ref,                                         \
-                                         _CONCAT(zmk_behavior_, DEVICE_DT_NAME_GET(node_id))) = {  \
-        .device = DEVICE_DT_GET(node_id),                                                          \
-    }
+#define BEHAVIOR_DEFINE(node_id) ZMK_BEHAVIOR_REF_DT_DEFINE(node_id)
 
 /**
  * @brief Like DEVICE_DT_DEFINE(), but also registers the device as a behavior.
@@ -89,6 +157,52 @@ struct zmk_behavior_ref {
     DEVICE_DT_INST_DEFINE(inst, __VA_ARGS__);                                                      \
     BEHAVIOR_DEFINE(DT_DRV_INST(inst))
 
+/**
+ * @brief Validate a given behavior binding is valid, including parameter validation
+ * if the metadata feature is enablued.
+ *
+ * @param binding The behavior binding to validate.
+ *
+ * @retval 0 if the passed in binding is valid.
+ * @retval -ENODEV if the binding references a non-existant behavior.
+ * @retval -EINVAL if parameters are not valid for the behavior metadata.
+ */
+int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding);
+
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+int zmk_behavior_get_empty_param_metadata(const struct device *dev,
+                                          struct behavior_parameter_metadata *metadata);
+
+/**
+ * @brief Validate a given behavior parameters match the behavior metadata.
+ *
+ * @param metadata The behavior metadata to validate against
+ * @param param1 The first parameter value
+ * @param param2 The second parameter value
+ *
+ * @retval 0 if the passed in parameters are valid.
+ * @retval -ENODEV if metadata is NULL.
+ * @retval -EINVAL if parameters are not valid for the metadata.
+ */
+int zmk_behavior_check_params_match_metadata(const struct behavior_parameter_metadata *metadata,
+                                             uint32_t param1, uint32_t param2);
+/**
+ * @brief Validate a given behavior parameter matches the behavior metadata parameter values.
+ *
+ * @param values The values to validate against
+ * @param values_len How many values to check
+ * @param param The value to check.
+ *
+ * @retval 0 if the passed in parameter is valid.
+ * @retval -ENODEV if values is NULL.
+ * @retval -EINVAL if parameter is not valid for the value metadata.
+ */
+int zmk_behavior_validate_param_values(const struct behavior_parameter_value_metadata *values,
+                                       size_t values_len, uint32_t param);
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 /**
  * Syscall wrapper for zmk_behavior_get_binding().
  *
@@ -120,6 +234,40 @@ static inline int z_impl_behavior_keymap_binding_convert_central_state_dependent
     return api->binding_convert_central_state_dependent_params(binding, event);
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+/**
+ * @brief Determine where the behavior should be run
+ * @param behavior Pointer to the device structure for the driver instance.
+ *
+ * @retval Zero if successful.
+ * @retval Negative errno code if failure.
+ */
+__syscall int behavior_get_parameter_metadata(const struct device *behavior,
+                                              struct behavior_parameter_metadata *param_metadata);
+
+static inline int
+z_impl_behavior_get_parameter_metadata(const struct device *behavior,
+                                       struct behavior_parameter_metadata *param_metadata) {
+    if (behavior == NULL || param_metadata == NULL) {
+        return -EINVAL;
+    }
+
+    const struct behavior_driver_api *api = (const struct behavior_driver_api *)behavior->api;
+
+    if (api->get_parameter_metadata) {
+        return api->get_parameter_metadata(behavior, param_metadata);
+    } else if (api->parameter_metadata) {
+        *param_metadata = *api->parameter_metadata;
+    } else {
+        return -ENODEV;
+    }
+
+    return 0;
+}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 /**
  * @brief Determine where the behavior should be run
  * @param behavior Pointer to the device structure for the driver instance.
diff --git a/app/include/zmk/behavior.h b/app/include/zmk/behavior.h
index ab95fd8e728..016fa3bc063 100644
--- a/app/include/zmk/behavior.h
+++ b/app/include/zmk/behavior.h
@@ -12,7 +12,7 @@
 #define ZMK_BEHAVIOR_TRANSPARENT 1
 
 struct zmk_behavior_binding {
-    char *behavior_dev;
+    const char *behavior_dev;
     uint32_t param1;
     uint32_t param2;
 };
diff --git a/app/src/behavior.c b/app/src/behavior.c
index fa2005ff1af..7777155f404 100644
--- a/app/src/behavior.c
+++ b/app/src/behavior.c
@@ -11,6 +11,8 @@
 
 #include <drivers/behavior.h>
 #include <zmk/behavior.h>
+#include <zmk/hid.h>
+#include <zmk/matrix.h>
 
 #include <zephyr/logging/log.h>
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@@ -39,6 +41,150 @@ const struct device *z_impl_behavior_get_binding(const char *name) {
     return NULL;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+int zmk_behavior_get_empty_param_metadata(const struct device *dev,
+                                          struct behavior_parameter_metadata *metadata) {
+    metadata->sets_len = 0;
+    return 0;
+}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+static int validate_hid_usage(uint16_t usage_page, uint16_t usage_id) {
+    LOG_DBG("Validate usage %d in page %d", usage_id, usage_page);
+    switch (usage_page) {
+    case HID_USAGE_KEY:
+        if (usage_id == 0 || (usage_id > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE &&
+                              usage_id < LEFT_CONTROL && usage_id > RIGHT_GUI)) {
+            return -EINVAL;
+        }
+        break;
+    case HID_USAGE_CONSUMER:
+        if (usage_id >
+            COND_CODE_1(IS_ENABLED(CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC), (0xFF), (0xFFF))) {
+            return -EINVAL;
+        }
+        break;
+    default:
+        LOG_WRN("Unsupported HID usage page %d", usage_page);
+        return -EINVAL;
+    }
+
+    return 0;
+}
+
+#define PARAM_MATCHES 0
+
+static int check_param_matches_value(const struct behavior_parameter_value_metadata *value_meta,
+                                     uint32_t param) {
+    switch (value_meta->type) {
+    case BEHAVIOR_PARAMETER_VALUE_TYPE_NIL:
+        if (param == 0) {
+            return PARAM_MATCHES;
+        }
+        break;
+    case BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE:
+        if (validate_hid_usage(ZMK_HID_USAGE_PAGE(param), ZMK_HID_USAGE_ID(param)) >= 0) {
+            return PARAM_MATCHES;
+        }
+
+        break;
+    case BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_INDEX:
+        if (param >= 0 && param < ZMK_KEYMAP_LEN) {
+            return PARAM_MATCHES;
+        }
+        break;
+        /* TODO: Restore with HSV -> RGB refactor
+        case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV:
+            // TODO: No real way to validate? Maybe max brightness?
+            break;
+        */
+    case BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE:
+        if (param == value_meta->value) {
+            return PARAM_MATCHES;
+        }
+        break;
+    case BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE:
+        if (param >= value_meta->range.min && param <= value_meta->range.max) {
+            return PARAM_MATCHES;
+        }
+        break;
+    default:
+        LOG_WRN("Unknown type %d", value_meta->type);
+        break;
+    }
+
+    return -ENOTSUP;
+}
+
+int zmk_behavior_validate_param_values(const struct behavior_parameter_value_metadata *values,
+                                       size_t values_len, uint32_t param) {
+    if (values_len == 0) {
+        return -ENODEV;
+    }
+
+    for (int v = 0; v < values_len; v++) {
+        int ret = check_param_matches_value(&values[v], param);
+        if (ret >= 0) {
+            return ret;
+        }
+    }
+
+    return -EINVAL;
+}
+
+int zmk_behavior_check_params_match_metadata(const struct behavior_parameter_metadata *metadata,
+                                             uint32_t param1, uint32_t param2) {
+    if (!metadata || metadata->sets_len == 0) {
+        if (!metadata) {
+            LOG_ERR("No metadata to check against");
+        }
+
+        return (param1 == 0 && param2 == 0) ? 0 : -ENODEV;
+    }
+
+    for (int s = 0; s < metadata->sets_len; s++) {
+        const struct behavior_parameter_metadata_set *set = &metadata->sets[s];
+        int param1_ret =
+            zmk_behavior_validate_param_values(set->param1_values, set->param1_values_len, param1);
+        int param2_ret =
+            zmk_behavior_validate_param_values(set->param2_values, set->param2_values_len, param2);
+
+        if ((param1_ret >= 0 || (param1_ret == -ENODEV && param1 == 0)) &&
+            (param2_ret >= 0 || (param2_ret == -ENODEV && param2 == 0))) {
+            return 0;
+        }
+    }
+
+    return -EINVAL;
+}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding) {
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    const struct device *behavior = zmk_behavior_get_binding(binding->behavior_dev);
+
+    if (!behavior) {
+        return -ENODEV;
+    }
+
+    struct behavior_parameter_metadata metadata;
+    int ret = behavior_get_parameter_metadata(behavior, &metadata);
+
+    if (ret < 0) {
+        LOG_WRN("Failed getting metadata for %s: %d", binding->behavior_dev, ret);
+        return ret;
+    }
+
+    return zmk_behavior_check_params_match_metadata(&metadata, binding->param1, binding->param2);
+#else
+    return 0;
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+}
+
 #if IS_ENABLED(CONFIG_LOG)
 static int check_behavior_names(void) {
     // Behavior names must be unique, but we don't have a good way to enforce this
diff --git a/app/src/behaviors/behavior_backlight.c b/app/src/behaviors/behavior_backlight.c
index 3f836b73d76..d67ce2e7a8a 100644
--- a/app/src/behaviors/behavior_backlight.c
+++ b/app/src/behaviors/behavior_backlight.c
@@ -18,6 +18,82 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata no_arg_values[] = {
+    {
+        .display_name = "Toggle On/Off",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_TOG_CMD,
+    },
+    {
+        .display_name = "Turn On",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_ON_CMD,
+    },
+    {
+        .display_name = "Turn OFF",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_OFF_CMD,
+    },
+    {
+        .display_name = "Increase Brightness",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_INC_CMD,
+    },
+    {
+        .display_name = "Decrease Brightness",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_DEC_CMD,
+    },
+    {
+        .display_name = "Cycle Brightness",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_CYCLE_CMD,
+    },
+};
+
+static const struct behavior_parameter_value_metadata one_arg_p1_values[] = {
+    {
+        .display_name = "Set Brightness",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BL_SET_CMD,
+    },
+};
+
+static const struct behavior_parameter_value_metadata one_arg_p2_values[] = {
+    {
+        .display_name = "Brightness",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE,
+        .range =
+            {
+                .min = 0,
+                .max = 255,
+            },
+    },
+};
+
+static const struct behavior_parameter_metadata_set no_args_set = {
+    .param1_values = no_arg_values,
+    .param1_values_len = ARRAY_SIZE(no_arg_values),
+};
+
+static const struct behavior_parameter_metadata_set one_args_set = {
+    .param1_values = one_arg_p1_values,
+    .param1_values_len = ARRAY_SIZE(one_arg_p1_values),
+    .param2_values = one_arg_p2_values,
+    .param2_values_len = ARRAY_SIZE(one_arg_p2_values),
+};
+
+static const struct behavior_parameter_metadata_set sets[] = {no_args_set, one_args_set};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(sets),
+    .sets = sets,
+};
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static int behavior_backlight_init(const struct device *dev) { return 0; }
 
 static int
@@ -89,6 +165,9 @@ static const struct behavior_driver_api behavior_backlight_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
     .binding_released = on_keymap_binding_released,
     .locality = BEHAVIOR_LOCALITY_GLOBAL,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif
 };
 
 BEHAVIOR_DT_INST_DEFINE(0, behavior_backlight_init, NULL, NULL, NULL, POST_KERNEL,
diff --git a/app/src/behaviors/behavior_bt.c b/app/src/behaviors/behavior_bt.c
index 03bb7d8c898..f439e49b1cf 100644
--- a/app/src/behaviors/behavior_bt.c
+++ b/app/src/behaviors/behavior_bt.c
@@ -20,6 +20,74 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata no_arg_values[] = {
+    {
+        .display_name = "Next Profile",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BT_NXT_CMD,
+    },
+    {
+        .display_name = "Previous Profile",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BT_PRV_CMD,
+    },
+    {
+        .display_name = "Clear All Profiles",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BT_CLR_ALL_CMD,
+    },
+    {
+        .display_name = "Clear Selected Profile",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BT_CLR_CMD,
+    },
+};
+
+static const struct behavior_parameter_metadata_set no_args_set = {
+    .param1_values = no_arg_values,
+    .param1_values_len = ARRAY_SIZE(no_arg_values),
+};
+
+static const struct behavior_parameter_value_metadata prof_index_param1_values[] = {
+    {
+        .display_name = "Select Profile",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BT_SEL_CMD,
+    },
+    {
+        .display_name = "Disconnect Profile",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = BT_DISC_CMD,
+    },
+};
+
+static const struct behavior_parameter_value_metadata prof_index_param2_values[] = {
+    {
+        .display_name = "Profile",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_RANGE,
+        .range = {.min = 0, .max = ZMK_BLE_PROFILE_COUNT},
+    },
+};
+
+static const struct behavior_parameter_metadata_set profile_index_metadata_set = {
+    .param1_values = prof_index_param1_values,
+    .param1_values_len = ARRAY_SIZE(prof_index_param1_values),
+    .param2_values = prof_index_param2_values,
+    .param2_values_len = ARRAY_SIZE(prof_index_param2_values),
+};
+
+static const struct behavior_parameter_metadata_set metadata_sets[] = {no_args_set,
+                                                                       profile_index_metadata_set};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(metadata_sets),
+    .sets = metadata_sets,
+};
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
                                      struct zmk_behavior_binding_event event) {
     switch (binding->param1) {
@@ -54,6 +122,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
 static const struct behavior_driver_api behavior_bt_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
     .binding_released = on_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 BEHAVIOR_DT_INST_DEFINE(0, behavior_bt_init, NULL, NULL, NULL, POST_KERNEL,
diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c
index 57263d1c85e..1c050c44fe5 100644
--- a/app/src/behaviors/behavior_hold_tap.c
+++ b/app/src/behaviors/behavior_hold_tap.c
@@ -68,6 +68,12 @@ struct behavior_hold_tap_config {
     int32_t hold_trigger_key_positions[];
 };
 
+struct behavior_hold_tap_data {
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    struct behavior_parameter_metadata_set set;
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+};
+
 // this data is specific for each hold-tap
 struct active_hold_tap {
     int32_t position;
@@ -652,9 +658,52 @@ static int on_hold_tap_binding_released(struct zmk_behavior_binding *binding,
     return ZMK_BEHAVIOR_OPAQUE;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+static int hold_tap_parameter_metadata(const struct device *hold_tap,
+                                       struct behavior_parameter_metadata *param_metadata) {
+    const struct behavior_hold_tap_config *cfg = hold_tap->config;
+    struct behavior_hold_tap_data *data = hold_tap->data;
+    int err;
+    struct behavior_parameter_metadata child_meta;
+
+    err = behavior_get_parameter_metadata(zmk_behavior_get_binding(cfg->hold_behavior_dev),
+                                          &child_meta);
+    if (err < 0) {
+        LOG_WRN("Failed to get the hold behavior parameter: %d", err);
+        return err;
+    }
+
+    if (child_meta.sets_len > 0) {
+        data->set.param1_values = child_meta.sets[0].param1_values;
+        data->set.param1_values_len = child_meta.sets[0].param1_values_len;
+    }
+
+    err = behavior_get_parameter_metadata(zmk_behavior_get_binding(cfg->tap_behavior_dev),
+                                          &child_meta);
+    if (err < 0) {
+        LOG_WRN("Failed to get the tap behavior parameter: %d", err);
+        return err;
+    }
+
+    if (child_meta.sets_len > 0) {
+        data->set.param2_values = child_meta.sets[0].param1_values;
+        data->set.param2_values_len = child_meta.sets[0].param1_values_len;
+    }
+
+    param_metadata->sets = &data->set;
+    param_metadata->sets_len = 1;
+
+    return 0;
+}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static const struct behavior_driver_api behavior_hold_tap_driver_api = {
     .binding_pressed = on_hold_tap_binding_pressed,
     .binding_released = on_hold_tap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .get_parameter_metadata = hold_tap_parameter_metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 static int position_state_changed_listener(const zmk_event_t *eh) {
@@ -791,7 +840,7 @@ static int behavior_hold_tap_init(const struct device *dev) {
 }
 
 #define KP_INST(n)                                                                                 \
-    static struct behavior_hold_tap_config behavior_hold_tap_config_##n = {                        \
+    static const struct behavior_hold_tap_config behavior_hold_tap_config_##n = {                  \
         .tapping_term_ms = DT_INST_PROP(n, tapping_term_ms),                                       \
         .hold_behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(n, bindings, 0)),               \
         .tap_behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(n, bindings, 1)),                \
@@ -807,9 +856,10 @@ static int behavior_hold_tap_init(const struct device *dev) {
         .hold_trigger_key_positions = DT_INST_PROP(n, hold_trigger_key_positions),                 \
         .hold_trigger_key_positions_len = DT_INST_PROP_LEN(n, hold_trigger_key_positions),         \
     };                                                                                             \
-    BEHAVIOR_DT_INST_DEFINE(n, behavior_hold_tap_init, NULL, NULL, &behavior_hold_tap_config_##n,  \
-                            POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,                      \
-                            &behavior_hold_tap_driver_api);
+    static struct behavior_hold_tap_data behavior_hold_tap_data_##n = {};                          \
+    BEHAVIOR_DT_INST_DEFINE(n, behavior_hold_tap_init, NULL, &behavior_hold_tap_data_##n,          \
+                            &behavior_hold_tap_config_##n, POST_KERNEL,                            \
+                            CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_hold_tap_driver_api);
 
 DT_INST_FOREACH_STATUS_OKAY(KP_INST)
 
diff --git a/app/src/behaviors/behavior_key_press.c b/app/src/behaviors/behavior_key_press.c
index 566cfcfba77..b090401ec50 100644
--- a/app/src/behaviors/behavior_key_press.c
+++ b/app/src/behaviors/behavior_key_press.c
@@ -16,6 +16,27 @@
 
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata param_values[] = {
+    {
+        .display_name = "Key",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_HID_USAGE,
+    },
+};
+
+static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
+    .param1_values = param_values,
+    .param1_values_len = ARRAY_SIZE(param_values),
+}};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(param_metadata_set),
+    .sets = param_metadata_set,
+};
+
+#endif
+
 static int behavior_key_press_init(const struct device *dev) { return 0; };
 
 static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
@@ -31,7 +52,12 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
 }
 
 static const struct behavior_driver_api behavior_key_press_driver_api = {
-    .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
+    .binding_pressed = on_keymap_binding_pressed,
+    .binding_released = on_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+};
 
 #define KP_INST(n)                                                                                 \
     BEHAVIOR_DT_INST_DEFINE(n, behavior_key_press_init, NULL, NULL, NULL, POST_KERNEL,             \
diff --git a/app/src/behaviors/behavior_key_toggle.c b/app/src/behaviors/behavior_key_toggle.c
index 0dc0f5abfdc..d967af0146b 100644
--- a/app/src/behaviors/behavior_key_toggle.c
+++ b/app/src/behaviors/behavior_key_toggle.c
@@ -31,9 +31,34 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
     return 0;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata param_values[] = {
+    {
+        .display_name = "Key",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD,
+        .standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE,
+    },
+};
+
+static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
+    .param1_values = param_values,
+    .param1_values_len = ARRAY_SIZE(param_values),
+}};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(param_metadata_set),
+    .sets = param_metadata_set,
+};
+
+#endif
+
 static const struct behavior_driver_api behavior_key_toggle_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
     .binding_released = on_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 #define KT_INST(n)                                                                                 \
diff --git a/app/src/behaviors/behavior_macro.c b/app/src/behaviors/behavior_macro.c
index acffe3d8857..b535ed8be07 100644
--- a/app/src/behaviors/behavior_macro.c
+++ b/app/src/behaviors/behavior_macro.c
@@ -34,6 +34,10 @@ struct behavior_macro_trigger_state {
 struct behavior_macro_state {
     struct behavior_macro_trigger_state release_state;
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    struct behavior_parameter_metadata_set set;
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
     uint32_t press_bindings_count;
 };
 
@@ -209,9 +213,100 @@ static int on_macro_binding_released(struct zmk_behavior_binding *binding,
     return ZMK_BEHAVIOR_OPAQUE;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static void assign_values_to_set(enum param_source param_source,
+                                 struct behavior_parameter_metadata_set *set,
+                                 const struct behavior_parameter_value_metadata *values,
+                                 size_t values_len) {
+    if (param_source == PARAM_SOURCE_MACRO_1ST) {
+        set->param1_values = values;
+        set->param1_values_len = values_len;
+    } else {
+        set->param2_values = values;
+        set->param2_values_len = values_len;
+    }
+}
+
+// This function will dynamically determine the parameter metadata for a particular macro by
+// inspecting the macro *bindings* to see what behaviors in that list receive the macro parameters,
+// and then using the metadata from those behaviors for the macro itself.
+//
+// Care need be taken, where a behavior in the list takes two parameters, and the macro passes along
+// a value for the *second* parameter, we need to make sure we find the right metadata set for the
+// referenced behavior that matches the first parameter.
+static int get_macro_parameter_metadata(const struct device *macro,
+                                        struct behavior_parameter_metadata *param_metadata) {
+    const struct behavior_macro_config *cfg = macro->config;
+    struct behavior_macro_state *data = macro->data;
+    struct behavior_macro_trigger_state state = {0};
+
+    for (int i = 0; (i < cfg->count) && (!data->set.param1_values || !data->set.param2_values);
+         i++) {
+        if (handle_control_binding(&state, &cfg->bindings[i]) ||
+            (state.param1_source == PARAM_SOURCE_BINDING &&
+             state.param2_source == PARAM_SOURCE_BINDING)) {
+            continue;
+        }
+
+        LOG_DBG("checking %d for the given state", i);
+
+        struct behavior_parameter_metadata binding_meta;
+        int err = behavior_get_parameter_metadata(
+            zmk_behavior_get_binding(cfg->bindings[i].behavior_dev), &binding_meta);
+        if (err < 0 || binding_meta.sets_len == 0) {
+            LOG_WRN("Failed to fetch macro binding parameter details %d", err);
+            return -ENOTSUP;
+        }
+
+        // If both macro parameters get passed to this one entry, use
+        // the metadata for this behavior verbatim.
+        if (state.param1_source != PARAM_SOURCE_BINDING &&
+            state.param2_source != PARAM_SOURCE_BINDING) {
+            param_metadata->sets_len = binding_meta.sets_len;
+            param_metadata->sets = binding_meta.sets;
+            return 0;
+        }
+
+        if (state.param1_source != PARAM_SOURCE_BINDING) {
+            assign_values_to_set(state.param1_source, &data->set,
+                                 binding_meta.sets[0].param1_values,
+                                 binding_meta.sets[0].param1_values_len);
+        }
+
+        if (state.param2_source != PARAM_SOURCE_BINDING) {
+            // For the param2 metadata, we need to find a set that matches fully bound first
+            // parameter of our macro entry, and use the metadata from that set.
+            for (int s = 0; s < binding_meta.sets_len; s++) {
+                if (zmk_behavior_validate_param_values(binding_meta.sets[s].param1_values,
+                                                       binding_meta.sets[s].param1_values_len,
+                                                       cfg->bindings[i].param1) >= 0) {
+                    assign_values_to_set(state.param2_source, &data->set,
+                                         binding_meta.sets[s].param2_values,
+                                         binding_meta.sets[s].param2_values_len);
+                    break;
+                }
+            }
+        }
+
+        state.param1_source = PARAM_SOURCE_BINDING;
+        state.param2_source = PARAM_SOURCE_BINDING;
+    }
+
+    param_metadata->sets_len = 1;
+    param_metadata->sets = &data->set;
+
+    return 0;
+}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static const struct behavior_driver_api behavior_macro_driver_api = {
     .binding_pressed = on_macro_binding_pressed,
     .binding_released = on_macro_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .get_parameter_metadata = get_macro_parameter_metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 #define TRANSFORMED_BEHAVIORS(n)                                                                   \
diff --git a/app/src/behaviors/behavior_momentary_layer.c b/app/src/behaviors/behavior_momentary_layer.c
index 0c86e605b55..e27889df96f 100644
--- a/app/src/behaviors/behavior_momentary_layer.c
+++ b/app/src/behaviors/behavior_momentary_layer.c
@@ -15,6 +15,27 @@
 
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata param_values[] = {
+    {
+        .display_name = "Layer",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_LAYER_INDEX,
+    },
+};
+
+static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
+    .param1_values = param_values,
+    .param1_values_len = ARRAY_SIZE(param_values),
+}};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(param_metadata_set),
+    .sets = param_metadata_set,
+};
+
+#endif
+
 struct behavior_mo_config {};
 struct behavior_mo_data {};
 
@@ -33,7 +54,12 @@ static int mo_keymap_binding_released(struct zmk_behavior_binding *binding,
 }
 
 static const struct behavior_driver_api behavior_mo_driver_api = {
-    .binding_pressed = mo_keymap_binding_pressed, .binding_released = mo_keymap_binding_released};
+    .binding_pressed = mo_keymap_binding_pressed,
+    .binding_released = mo_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+};
 
 static const struct behavior_mo_config behavior_mo_config = {};
 
diff --git a/app/src/behaviors/behavior_none.c b/app/src/behaviors/behavior_none.c
index 0137622aceb..b1dc4ad3327 100644
--- a/app/src/behaviors/behavior_none.c
+++ b/app/src/behaviors/behavior_none.c
@@ -31,6 +31,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
 static const struct behavior_driver_api behavior_none_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
     .binding_released = on_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 BEHAVIOR_DT_INST_DEFINE(0, behavior_none_init, NULL, NULL, NULL, POST_KERNEL,
diff --git a/app/src/behaviors/behavior_outputs.c b/app/src/behaviors/behavior_outputs.c
index d172c3a11b8..ffa57d16357 100644
--- a/app/src/behaviors/behavior_outputs.c
+++ b/app/src/behaviors/behavior_outputs.c
@@ -20,6 +20,42 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata std_values[] = {
+    {
+        .value = OUT_TOG,
+        .display_name = "Toggle Outputs",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+    },
+#if IS_ENABLED(CONFIG_ZMK_USB)
+    {
+        .value = OUT_USB,
+        .display_name = "USB Output",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+    },
+#endif // IS_ENABLED(CONFIG_ZMK_USB)
+#if IS_ENABLED(CONFIG_ZMK_BLE)
+    {
+        .value = OUT_BLE,
+        .display_name = "BLE Output",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+    },
+#endif // IS_ENABLED(CONFIG_ZMK_BLE)
+};
+
+static const struct behavior_parameter_metadata_set std_set = {
+    .param1_values = std_values,
+    .param1_values_len = ARRAY_SIZE(std_values),
+};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = 1,
+    .sets = &std_set,
+};
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
                                      struct zmk_behavior_binding_event event) {
     switch (binding->param1) {
@@ -40,6 +76,9 @@ static int behavior_out_init(const struct device *dev) { return 0; }
 
 static const struct behavior_driver_api behavior_outputs_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 BEHAVIOR_DT_INST_DEFINE(0, behavior_out_init, NULL, NULL, NULL, POST_KERNEL,
diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c
index a16ee591ed4..c37e5217c73 100644
--- a/app/src/behaviors/behavior_rgb_underglow.c
+++ b/app/src/behaviors/behavior_rgb_underglow.c
@@ -18,6 +18,119 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
 
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata no_arg_values[] = {
+    {
+        .display_name = "Toggle On/Off",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_TOG_CMD,
+    },
+    {
+        .display_name = "Turn On",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_ON_CMD,
+    },
+    {
+        .display_name = "Turn OFF",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_OFF_CMD,
+    },
+    {
+        .display_name = "Hue Up",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_HUI_CMD,
+    },
+    {
+        .display_name = "Hue Down",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_HUD_CMD,
+    },
+    {
+        .display_name = "Saturation Up",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_SAI_CMD,
+    },
+    {
+        .display_name = "Saturation Down",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_SAD_CMD,
+    },
+    {
+        .display_name = "Brightness Up",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_BRI_CMD,
+    },
+    {
+        .display_name = "Brightness Down",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_BRD_CMD,
+    },
+    {
+        .display_name = "Speed Up",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_SPI_CMD,
+    },
+    {
+        .display_name = "Speed Down",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_SPD_CMD,
+    },
+    {
+        .display_name = "Next Effect",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_EFF_CMD,
+    },
+    {
+        .display_name = "Previous Effect",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_EFR_CMD,
+    },
+};
+
+static const struct behavior_parameter_metadata_set no_args_set = {
+    .param1_values = no_arg_values,
+    .param1_values_len = ARRAY_SIZE(no_arg_values),
+};
+
+/*
+static const struct behavior_parameter_value_metadata hsv_p1_value_metadata_values[] = {
+    {
+        .display_name = "Set Color",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_VALUE,
+        .value = RGB_COLOR_HSB_CMD,
+    },
+};
+
+static const struct behavior_parameter_value_metadata hsv_p2_value_metadata_values[] = {
+    {
+        .display_name = "Color",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD,
+        .standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV,
+    },
+};
+
+static const struct behavior_parameter_metadata_set hsv_value_metadata_set = {
+    .param1_values = hsv_p1_value_metadata_values,
+    .param1_values_len = ARRAY_SIZE(hsv_p1_value_metadata_values),
+    .param_values = hsv_p2_value_metadata_values,
+    .param_values_len = ARRAY_SIZE(hsv_p2_value_metadata_values),
+};
+
+*/
+
+static const struct behavior_parameter_metadata_set sets[] = {
+    no_args_set,
+    // hsv_value_metadata_set,
+};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(sets),
+    .sets = sets,
+};
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static int behavior_rgb_underglow_init(const struct device *dev) { return 0; }
 
 static int
@@ -147,6 +260,9 @@ static const struct behavior_driver_api behavior_rgb_underglow_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
     .binding_released = on_keymap_binding_released,
     .locality = BEHAVIOR_LOCALITY_GLOBAL,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif
 };
 
 BEHAVIOR_DT_INST_DEFINE(0, behavior_rgb_underglow_init, NULL, NULL, NULL, POST_KERNEL,
diff --git a/app/src/behaviors/behavior_soft_off.c b/app/src/behaviors/behavior_soft_off.c
index 461ce933cf7..fcffd09ae5e 100644
--- a/app/src/behaviors/behavior_soft_off.c
+++ b/app/src/behaviors/behavior_soft_off.c
@@ -74,6 +74,9 @@ static const struct behavior_driver_api behavior_soft_off_driver_api = {
     .binding_pressed = on_keymap_binding_pressed,
     .binding_released = on_keymap_binding_released,
     .locality = BEHAVIOR_LOCALITY_GLOBAL,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 #define BSO_INST(n)                                                                                \
diff --git a/app/src/behaviors/behavior_sticky_key.c b/app/src/behaviors/behavior_sticky_key.c
index b0e9f3ed0c0..d1299c78d7f 100644
--- a/app/src/behaviors/behavior_sticky_key.c
+++ b/app/src/behaviors/behavior_sticky_key.c
@@ -188,9 +188,41 @@ static int on_sticky_key_binding_released(struct zmk_behavior_binding *binding,
     return ZMK_BEHAVIOR_OPAQUE;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static int sticky_key_parameter_domains(const struct device *sk,
+                                        struct behavior_parameter_metadata *param_metadata) {
+    const struct behavior_sticky_key_config *cfg = sk->config;
+
+    struct behavior_parameter_metadata child_metadata;
+
+    int err = behavior_get_parameter_metadata(zmk_behavior_get_binding(cfg->behavior.behavior_dev),
+                                              &child_metadata);
+    if (err < 0) {
+        LOG_WRN("Failed to get the sticky key bound behavior parameter: %d", err);
+    }
+
+    for (int s = 0; s < child_metadata.sets_len; s++) {
+        const struct behavior_parameter_metadata_set *set = &child_metadata.sets[s];
+
+        if (set->param2_values_len > 0) {
+            return -ENOTSUP;
+        }
+    }
+
+    *param_metadata = child_metadata;
+
+    return 0;
+}
+
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
 static const struct behavior_driver_api behavior_sticky_key_driver_api = {
     .binding_pressed = on_sticky_key_binding_pressed,
     .binding_released = on_sticky_key_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .get_parameter_metadata = sticky_key_parameter_domains,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh);
@@ -337,7 +369,7 @@ struct behavior_sticky_key_data {};
 static struct behavior_sticky_key_data behavior_sticky_key_data;
 
 #define KP_INST(n)                                                                                 \
-    static struct behavior_sticky_key_config behavior_sticky_key_config_##n = {                    \
+    static const struct behavior_sticky_key_config behavior_sticky_key_config_##n = {              \
         .behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, DT_DRV_INST(n)),                                 \
         .release_after_ms = DT_INST_PROP(n, release_after_ms),                                     \
         .quick_release = DT_INST_PROP(n, quick_release),                                           \
diff --git a/app/src/behaviors/behavior_tap_dance.c b/app/src/behaviors/behavior_tap_dance.c
index 4f6fa1a134e..ce57b70fc4b 100644
--- a/app/src/behaviors/behavior_tap_dance.c
+++ b/app/src/behaviors/behavior_tap_dance.c
@@ -189,6 +189,9 @@ void behavior_tap_dance_timer_handler(struct k_work *item) {
 static const struct behavior_driver_api behavior_tap_dance_driver_api = {
     .binding_pressed = on_tap_dance_binding_pressed,
     .binding_released = on_tap_dance_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 static int tap_dance_position_state_changed_listener(const zmk_event_t *eh);
diff --git a/app/src/behaviors/behavior_to_layer.c b/app/src/behaviors/behavior_to_layer.c
index 1c87a925905..d260087efce 100644
--- a/app/src/behaviors/behavior_to_layer.c
+++ b/app/src/behaviors/behavior_to_layer.c
@@ -32,9 +32,34 @@ static int to_keymap_binding_released(struct zmk_behavior_binding *binding,
     return ZMK_BEHAVIOR_OPAQUE;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata param_values[] = {
+    {
+        .display_name = "Layer",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD,
+        .standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX,
+    },
+};
+
+static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
+    .param1_values = param_values,
+    .param1_values_len = ARRAY_SIZE(param_values),
+}};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(param_metadata_set),
+    .sets = param_metadata_set,
+};
+
+#endif
+
 static const struct behavior_driver_api behavior_to_driver_api = {
     .binding_pressed = to_keymap_binding_pressed,
     .binding_released = to_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 BEHAVIOR_DT_INST_DEFINE(0, behavior_to_init, NULL, NULL, NULL, POST_KERNEL,
diff --git a/app/src/behaviors/behavior_toggle_layer.c b/app/src/behaviors/behavior_toggle_layer.c
index 817462df5aa..df261ed3a34 100644
--- a/app/src/behaviors/behavior_toggle_layer.c
+++ b/app/src/behaviors/behavior_toggle_layer.c
@@ -34,9 +34,34 @@ static int tog_keymap_binding_released(struct zmk_behavior_binding *binding,
     return ZMK_BEHAVIOR_OPAQUE;
 }
 
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+
+static const struct behavior_parameter_value_metadata param_values[] = {
+    {
+        .display_name = "Layer",
+        .type = BEHAVIOR_PARAMETER_VALUE_TYPE_STANDARD,
+        .standard = BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX,
+    },
+};
+
+static const struct behavior_parameter_metadata_set param_metadata_set[] = {{
+    .param1_values = param_values,
+    .param1_values_len = ARRAY_SIZE(param_values),
+}};
+
+static const struct behavior_parameter_metadata metadata = {
+    .sets_len = ARRAY_SIZE(param_metadata_set),
+    .sets = param_metadata_set,
+};
+
+#endif
+
 static const struct behavior_driver_api behavior_tog_driver_api = {
     .binding_pressed = tog_keymap_binding_pressed,
     .binding_released = tog_keymap_binding_released,
+#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
+    .parameter_metadata = &metadata,
+#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
 };
 
 static const struct behavior_tog_config behavior_tog_config = {};