diff --git a/quirks_v2.md b/quirks_v2.md
new file mode 100644
index 0000000000..854acca5e9
--- /dev/null
+++ b/quirks_v2.md
@@ -0,0 +1,499 @@
+# Quirks v2
+
+## Introduction
+
+Quirks v2 use a fluent interface style. While this isn't common in python it was done to improve the readability of the source code and to make it human-friendly for non developers. The amount of boilerplate code has been significantly reduced and the need to specify a signature dictionary and a replacement dictionary has been removed. This should make it much easier for the community to contribute quirks.
+
+## Goals
+
+- Significantly reduce the amount of boilerplate code required to write a quirk
+- Make it easier for the community to contribute quirks
+- Make it easier to read and understand quirks
+- Make it easier to maintain quirks
+- Expose entities from a quirk
+- Allow custom logic to determine if a quirk should be applied to a device
+- Allow custom binding, reporting configuration or any sort of initialization logic without hacking the bind or configure_reporting methods
+
+## QuirksV2RegistryEntry
+
+
+ add_to_registry_v2
+
+This method is used to add a quirk to the registry. It takes two arguments, the manufacturer and the model. It returns a `QuirksV2RegistryEntry` object.
+
+
+
+The `QuirksV2RegistryEntry` class is used to build up a quirk. It has a number of methods that can be chained together to build up the quirk. The `QuirksV2RegistryEntry` class has the following methods:
+
+### Device matching methods
+
+
+ also_applies_to
+
+This method allows specifying additional manufacturer and models that the quirk should apply to.
+
+```python
+"""
+Register this quirks v2 entry for an additional manufacturer and model.
+
+Args:
+ manufacturer (str): The manufacturer of the device.
+ model (str): The model of the device.
+
+Returns:
+ QuirksV2RegistryEntry: The updated QuirksV2RegistryEntry object.
+
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L600-L627
+
+
+
+
+ filter
+
+This method allows specifying a custom filter function that determines if the quirk should be applied to a device.
+
+```python
+"""Add a filter and returns self.
+
+Args:
+ filter_function (FilterType): The filter function to be added. It should take a single argument, a zigpy.device.Device instance, and return a boolean if the condition the filter is testing passes.
+
+Returns:
+ QuirksV2RegistryEntry: The instance of the QuirksV2RegistryEntry class.
+
+Example:
+ def some_filter(device: zigpy.device.Device) -> bool:
+ # Your filter logic here
+
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L76-L118
+
+
+
+### Device modification methods
+
+
+ adds
+This method allows adding a cluster to a device when the quirk is applied.
+
+```python
+"""
+Add an AddsMetadata entry and return self.
+
+This method allows adding a cluster to a device when the quirk is applied.
+
+Args:
+ cluster (int | type[Cluster | CustomCluster]): The cluster ID or a subclass of Cluster or CustomCluster.
+ cluster_type (ClusterType, optional): The type of cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The endpoint ID. Defaults to 1.
+ constant_attributes (dict[ZCLAttributeDef, typing.Any] | None, optional):
+ A dictionary of ZCLAttributeDef instances and their values.
+ These attributes will be added to the cluster when the quirk is applied and the values will be constant.
+ Defaults to None.
+
+Returns:
+ QuirksV2RegistryEntry: The updated instance of QuirksV2RegistryEntry.
+
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L359-L387
+
+
+
+
+ removes
+
+This method allows removing a cluster from a device when the quirk is applied.
+
+```python
+"""Add a RemovesMetadata entry and returns self.
+
+Args:
+ cluster_id (int): The ID of the cluster to be removed.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+
+Returns:
+ QuirksV2RegistryEntry: The updated instance of QuirksV2RegistryEntry.
+
+This method allows removing a cluster from a device when the quirk is applied.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L304-L313
+
+
+
+
+ replaces
+
+This method allows replacing a cluster on a device when the quirk is applied.
+
+```python
+"""Add a ReplacesMetadata entry and returns self.
+
+Args:
+ replacement_cluster_class (type[Cluster | CustomCluster]): A subclass of Cluster or CustomCluster that will be used to create a new cluster instance to replace the existing cluster.
+ cluster_id (int | None, optional): The cluster_id for the cluster to be removed. If not provided, the cluster_id of the replacement cluster will be used. Defaults to None.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The endpoint_id of the cluster. Defaults to 1.
+
+Returns:
+ QuirksV2RegistryEntry: The updated instance of the QuirksV2RegistryEntry class.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L817-L824
+
+
+
+
+ device_class
+
+This method allows specifying a subclass of CustomDeviceV2 for a device when the quirk is applied.
+
+```python
+"""Set the custom device class to be used in this quirk and returns self.
+
+Args:
+ custom_device_class (type[CustomDeviceV2]): The custom device class to be used in this quirk.
+ It must be a subclass of CustomDeviceV2.
+
+Returns:
+ QuirksV2RegistryEntry: The instance of the QuirksV2RegistryEntry class.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L226-L243
+
+
+
+
+ node_descriptor
+
+This method allows specifying a custom node descriptor for a device when the quirk is applied.
+
+```python
+"""Set the node descriptor and returns self.
+
+Args:
+ node_descriptor (NodeDescriptor): The node descriptor to be set.
+
+Returns:
+ QuirksV2RegistryEntry: The updated instance of QuirksV2RegistryEntry.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L250-L279
+
+
+
+
+ skip_configuration
+
+This method allows skipping the reporting configuration for all clusters on this device.
+
+```python
+"""Set the skip_configuration flag and returns self.
+
+Args:
+ skip_configuration (bool, optional): If True, reporting configuration will not be
+ applied to any cluster on this device. Defaults to True.
+
+Returns:
+ QuirksV2RegistryEntry: The instance of the QuirksV2RegistryEntry class.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L286-L297
+
+
+
+
+ device_automation_triggers
+
+This method allows specifying device automation triggers for a device when the quirk is applied.
+
+
+
+### Methods for exposing entities
+
+
+ enum
+
+This method allows exposing an enum based entity in Home Assistant.
+
+```python
+"""Add an EntityMetadata containing ZCLEnumMetadata and return self.
+
+This method allows exposing an enum based entity in Home Assistant.
+
+Args:
+ attribute_name (str): The name of the ZCL attribute this entity uses for its value.
+ enum_class (type[Enum]): The class of the enum to use for the entity.
+ cluster_id (int): The ID of the cluster.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ entity_type (EntityType, optional): The type of the entity. Defaults to EntityType.CONFIG.
+ entity_platform (EntityPlatform, optional): The platform of the entity. Defaults to EntityPlatform.SELECT.
+ initially_disabled (bool, optional): Whether the entity is initially disabled. Defaults to False.
+ attribute_initialized_from_cache (bool, optional): Whether the attribute is initialized from the cluster cache. Defaults to True.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The modified QuirksV2RegistryEntry object.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L103-L118
+
+
+
+
+ sensor
+
+This method allows exposing a sensor entity in Home Assistant.
+
+```python
+"""Add an EntityMetadata containing ZCLSensorMetadata and return self.
+
+This method allows exposing a sensor entity in Home Assistant.
+
+Args:
+ attribute_name (str): The name of the ZCL attribute this entity uses for its value.
+ cluster_id (int): The ID of the cluster.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ divisor (int, optional): The divisor for the sensor value. Defaults to 1.
+ multiplier (int, optional): The multiplier for the sensor value. Defaults to 1.
+ entity_type (EntityType, optional): The type of the entity. Defaults to EntityType.STANDARD.
+ device_class (SensorDeviceClass | None, optional): The device class of the sensor. Defaults to None.
+ state_class (SensorStateClass | None, optional): The state class of the sensor. Defaults to None.
+ unit (str | None, optional): The unit of measurement for the sensor. Defaults to None.
+ initially_disabled (bool, optional): Whether the sensor is initially disabled. Defaults to False.
+ attribute_initialized_from_cache (bool, optional): Whether the attribute is initialized from the cluster cache. Defaults to True.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The updated QuirksV2RegistryEntry object.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L359-L387
+
+
+
+
+ binary_sensor
+
+This method allows exposing a binary sensor entity in Home Assistant.
+
+```python
+"""Add an EntityMetadata containing BinarySensorMetadata and return self.
+
+This method allows exposing a binary sensor entity in Home Assistant.
+
+Args:
+ attribute_name (str): The name of the attribute.
+ cluster_id (int): The ID of the cluster.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ device_class (BinarySensorDeviceClass | None, optional): The device class of the binary sensor. Defaults to None.
+ initially_disabled (bool, optional): Whether the binary sensor is initially disabled. Defaults to False.
+ attribute_initialized_from_cache (bool, optional): Whether the attribute is initialized from the cluster cache. Defaults to True.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The registry entry for the binary sensor.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L482-L511
+
+
+
+
+ switch
+
+This method allows exposing a switch entity in Home Assistant.
+
+```python
+"""Add an EntityMetadata containing SwitchMetadata and return self.
+
+This method allows exposing a switch entity in Home Assistant.
+
+Args:
+ attribute_name (str): The name of the attribute.
+ cluster_id (int): The ID of the cluster.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ force_inverted (bool, optional): Whether to force the attribute to be inverted. Defaults to False.
+ invert_attribute_name (str | None, optional): The name of the attribute to invert. Defaults to None.
+ off_value (int, optional): The value representing the off state. Defaults to 0.
+ on_value (int, optional): The value representing the on state. Defaults to 1.
+ entity_platform (EntityPlatform, optional): The platform of the entity. Defaults to EntityPlatform.SWITCH.
+ initially_disabled (bool, optional): Whether the entity is initially disabled. Defaults to False.
+ attribute_initialized_from_cache (bool, optional): Whether the attribute is initialized from the cluster cache. Defaults to True.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The updated QuirksV2RegistryEntry object.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L394-L429
+
+
+
+
+ number
+
+This method allows exposing a number entity in Home Assistant.
+
+```python
+"""Add an EntityMetadata containing NumberMetadata and return self.
+
+This method allows exposing a number entity in Home Assistant.
+
+Args:
+ attribute_name (str): The name of the attribute.
+ cluster_id (int): The ID of the cluster.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ min_value (float | None, optional): The minimum value of the number. Defaults to None.
+ max_value (float | None, optional): The maximum value of the number. Defaults to None.
+ step (float | None, optional): The step value of the number. Defaults to None.
+ unit (str | None, optional): The unit of the number. Defaults to None.
+ mode (str | None, optional): The mode of the number. Defaults to None.
+ multiplier (float | None, optional): The multiplier of the number. Defaults to None.
+ device_class (NumberDeviceClass | None, optional): The device class of the number. Defaults to None.
+ initially_disabled (bool, optional): Whether the number is initially disabled. Defaults to False.
+ attribute_initialized_from_cache (bool, optional): Whether the attribute is initialized from the cluster cache. Defaults to True.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The modified QuirksV2RegistryEntry object.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L436-L475
+
+
+
+
+ write_attr_button
+
+This method allows exposing a button entity in Home Assistant that writes a value to an attribute when pressed.
+
+```python
+"""Add an EntityMetadata containing WriteAttributeButtonMetadata and return self.
+
+This method allows exposing a button entity in Home Assistant that writes
+a value to an attribute when pressed.
+
+Args:
+ attribute_name (str): The name of the attribute to write.
+ attribute_value (int): The value to write to the attribute.
+ cluster_id (int): The ID of the cluster.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ entity_type (EntityType, optional): The type of the entity. Defaults to EntityType.CONFIG.
+ initially_disabled (bool, optional): Whether the entity is initially disabled. Defaults to False.
+ attribute_initialized_from_cache (bool, optional): Whether the attribute is initialized from the cluster cache. Defaults to True.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The modified QuirksV2RegistryEntry object.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L518-L551
+
+
+
+
+ command_button
+
+This method allows exposing a button entity in Home Assistant that executes a ZCL command when pressed.
+
+```python
+"""Add an EntityMetadata containing ZCLCommandButtonMetadata and return self.
+
+This method allows exposing a button entity in Home Assistant that executes
+a ZCL command when pressed.
+
+Args:
+ command_name (str): The name of the ZCL command to be executed.
+ cluster_id (int): The ID of the cluster to which the command belongs.
+ command_args (tuple | None, optional): The arguments to be passed to the command. Defaults to None.
+ command_kwargs (dict[str, Any] | None, optional): The keyword arguments to be passed to the command. Defaults to None.
+ cluster_type (ClusterType, optional): The type of the cluster. Defaults to ClusterType.Server.
+ endpoint_id (int, optional): The ID of the endpoint. Defaults to 1.
+ entity_type (EntityType, optional): The type of the entity. Defaults to EntityType.CONFIG.
+ initially_disabled (bool, optional): Whether the button is initially disabled. Defaults to False.
+ translation_key (str | None, optional): The translation key for the entity. Defaults to None. If not provided, the attribute_name will be used.
+
+Returns:
+ QuirksV2RegistryEntry: The updated QuirksV2RegistryEntry object.
+"""
+```
+
+Example:
+
+https://github.com/zigpy/zigpy/blob/6062e41d28ad99bafa9dd803cab1c3ae4a8fe6ba/tests/test_quirks_v2.py#L558-L593
+
+
+
+## Breakdown of a minimal example
+
+```python
+from zigpy.quirks.v2 import add_to_registry_v2
+
+(
+ add_to_registry_v2("IKEA of Sweden", "TRADFRI remote control")
+ .replaces(PowerConfig1CRCluster)
+ .replaces(ScenesCluster, cluster_type=ClusterType.Client)
+)
+```
+
+`add_to_registry_v2` this method is used to add a quirk to the registry. It takes two arguments, the manufacturer and the model. It returns a `QuirksV2RegistryEntry` object. This object has a number of methods that can be chained together to build up the quirk.
+
+`replaces` this method is used to specify the cluster that the quirk should replace.
+
+This quirk would have looked like this in the original quirks system:
+
+https://github.com/zigpy/zha-device-handlers/blob/e4d0663aa4fb2f4bd0b41825f1942e3d29893375/zhaquirks/ikea/fivebtnremote.py#L387-L447