> ## Content Index
> Fetch the complete content index at: https://codetraveler.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Introducing Bindable Property Source Generators
- URL: https://codetraveler.io/2026/01/29/introducing-bindable-property-source-generators/
- Published: 2026-01-29T10:11:46.000Z
- Updated: 2026-02-05T19:27:22.000Z
- Author: Brandon Minnick

I am excited to announce two new source generators introduced in `CommunityToolkit.Maui v14.0.0` :

- `[BindableProperty]`
- `[AttachedBindableProperty<T>]`

These new source generators make it easier than ever to create a `BindableProperty` in your .NET MAUI apps. In fact, all Bindable Properties in `CommunityToolkit.Maui` are now automatically generated using these new source generators.

Using these attributes, the .NET MAUI Community Toolkit will generate all of the boiler-plate code required for Bindable Properties and Attached Properties.

## Opt-into this Experimental Feature

We have decided to release this feature using the `[Experimental]` attribute. This allows us to let the community test its features and provide feedback while giving us the flexibility to modify the API as requested.

1. In the `csproj` file of your .NET MAUI app, suppress the `MCTEXP001` :

```xml
<!-- Opt into Bindable Property Source Generators -->
<PropertyGroup>
    <NoWarn>MCTEXP001</NoWarn>
</PropertyGroup>
```

In the mean-time, I will be writing more comprehensive documentation and writing Analyzers, similar to `CommunityToolkit.MVVM`, to help provide you the best coding experience! 

## Using `[BindableProperty]`

To leverage the Bindable Property Source Generator, first ensure you using a `partial class.` Then, add the `partial` keyword and attach the `[CommunityToolkit.Maui.BindableProperty]` attribute to your Property:

1. Add the `partial` keyword to the `class`
2. Add the `partial` keyword the Property for which to generate an associated Bindable Property
3. Add the `[CommunityToolkit.Maui.BindableProperty]` attribute to the Property for which to generate an associated Bindable Property

Here is an example:

```csharp
using CommunityToolkit.Maui;

namespace BindablePropertySourceGeneratorSample;

public partial class CustomButton : Button
{
    [CommunityToolkit.Maui.BindableProperty] 
    public partial int? Number { get; set; }
}
```

The above example generates the following Bindable Property:

```csharp
public partial class CustomButton
{
    /// <summary>
    /// BindableProperty for the <see cref = "Number"/> property.
    /// </summary>
    public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Number", typeof(int?), typeof(BindablePropertySourceGeneratorSample.CustomButton), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
    public partial int? Number { get => (int)GetValue(NumberProperty); set => SetValue(NumberProperty, value); }
}
```

### Setting Default Value

To set the default value for `[BindableProperty]`, simply set the initializer on the partial property:

```csharp
[CommunityToolkit.Maui.BindableProperty] 
public partial int Number { get; set; } = 10;
```

### Setting Default `BindingMode`

To set the default `BindingMode` for `[BindableProperty]`, assign the value to the attribute's `DefaultBindingMode` property:

```csharp
[CommunityToolkit.Maui.BindableProperty(DefaultBindingMode = BindingMode.TwoWay)] 
public partial int Number { get; set; }
```

### Using `ValidateValue` 

To leverage the `ValidateValueDelegate`, use the `nameof()` operator to assign the name of the validation method to the the attribute's `ValidateValueMethodName` property:

```csharp
[CommunityToolkit.Maui.BindableProperty(ValidateValueMethodName = nameof(ValidateNumber))] 
public partial int Number { get; set; }

static bool ValidateNumber(BindableObject bindable, object value)
{
    var updatedValue = (int)value;
    return updatedValue > 0;
}
```

> **Important:** The method used for `ValidateValueMethodName` must adhere to the [ValidateValueDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.validatevaluedelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static bool MethodName(BindableObject, object)`

### Using `PropertyChanging` 

To leverage the `BindingPropertyChangingDelegate`, use the `nameof()` operator to assign the name of the property changing method to the the attribute's `PropertyChangingMethodName` property:

```csharp
[CommunityToolkit.Maui.BindableProperty(PropertyChangingMethodName = nameof(OnNumberChanging))] 
public partial int Number { get; set; }

static void OnNumberChanging(BindableObject bindable, object oldValue, object newValue)
{
  Trace.WriteLine($"Old Value: {oldValue}");
  Trace.WriteLine($"New Value: {newValue}");
}
```

> **Important:** The method used for `PropertyChangingMethodName` must adhere to the [BindingPropertyChangingDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.bindingpropertychangingdelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static void MethodName(BindableObject, object, object)`

### Using `PropertyChanged` 

To leverage the `BindingPropertyChangedDelegate`, use the `nameof()` operator to assign the name of the property changed method to the the attribute's `PropertyChangedMethodName` property:

```csharp
[CommunityToolkit.Maui.BindableProperty(PropertyChangedMethodName = nameof(OnNumberChanged))] 
public partial int Number { get; set; }

static void OnNumberChanged(BindableObject bindable, object oldValue, object newValue)
{
  Trace.WriteLine($"Old Value: {oldValue}");
  Trace.WriteLine($"New Value: {newValue}");
}
```

> **Important:** The method used for `PropertyChangedMethodName` must adhere to the [BindingPropertyChangedDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.bindingpropertychangeddelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static void MethodName(BindableObject, object, object)`

### Using `CoreceValue` 

To leverage the `CoerceValueDelegate`, use the `nameof()` operator to assign the name of the coerce value changed method to the the attribute's `CoerceValueMethodName` property:

```csharp
[CommunityToolkit.Maui.BindableProperty(CoerceValueMethodName = nameof(CoerceNumber))] 
public partial int Number { get; set; }

static object CoerceNumber(BindableObject bindable, object value)
{
    var updatedValue = (int)value;
    return Math.Clamp(updatedValue, 0, int.MaxValue);
}
```

> **Important:** The method used for `CoerceValueMethodName` must adhere to the [CoreceValueDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.coercevaluedelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static object MethodName(BindableObject, object)`

### Creating a Readonly Bindable Property

The `[BindableProperty]` attribute will automatically generate a Readonly Bindable Property (eg [BindableProperty.CreateReadOnly()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.createreadonly?view=net-maui-10.0&ref=codetraveler.io)) when the accessibility modifier of the Property's setter is `protected`, `private protected` or `private`.

For example, a Readonly Bindable Property will be generated for the following properties whose setter cannot be accessed outside of its class:

```csharp
public partial class CustomButton : Button
{
    [CommunityToolkit.Maui.BindableProperty] 
    public partial int Number { get; protected set; }
    
    [CommunityToolkit.Maui.BindableProperty] 
    public partial char Letter { get; private protected set; }
    
    [CommunityToolkit.Maui.BindableProperty] 
    public partial string Text { get; private set; }
}
```

## Using `[AttachedBindableProperty<T>]`

Using `[AttachedBindableProperty<T>]` is a bit different. This attribute is applied to either the Class or the Constructor, not a Property, as [Attached Properties don't have an associated Property](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/attached-properties?ref=codetraveler.io):

> .NET Multi-platform App UI (.NET MAUI) attached properties enable an object to assign a value for a property that its own class doesn't define

To leverage the Attached Bindable Property Source Generator, first ensure you using a `partial class.` Then, add `[CommunityToolkit.Maui.AttachedBindableProperty<T>]` attribute to either the class or constructor:

1. Add the `partial` keyword to the `class`
2. Add the `[CommunityToolkit.Maui.AttachedBindableProperty<T>]` attribute to the class or constructor for which to generate an associated Attached Bindable Property

Here is an example:

```csharp
namespace BindablePropertySourceGeneratorSample;

[AttachedBindableProperty<int>("Number")] 
public partial class CustomButton : Button
{
  public CustomButton()
  {
  }
}
```

Alternatively, the attribute can be attached to the constructor:

```csharp
namespace BindablePropertySourceGeneratorSample;
 
public partial class CustomButton : Button
{
  [AttachedBindableProperty<int>("Number")]
  public CustomButton()
  {
  }
}
```

Both examples above generate the following Attached Bindable Property along with its associated Get/Set methods

```csharp
public partial class CustomButton
{
    /// <summary>
    /// Attached BindableProperty for the Number property.
    /// </summary>
    public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Number", typeof(int), typeof(BindablePropertySourceGeneratorSample.CustomButton), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null);
    /// <summary>
    /// Gets Number for the <paramref name = "bindable"/> child element.
    /// </summary>
    public static int GetNumber(global::Microsoft.Maui.Controls.BindableObject bindable) => (int)bindable.GetValue(NumberProperty);
    /// <summary>
    /// Sets Number for the <paramref name = "bindable"/> child element.
    /// </summary>
    public static void SetNumber(global::Microsoft.Maui.Controls.BindableObject bindable, int value) => bindable.SetValue(NumberProperty, value);
}
```

### Nullability

In C#, `Attribute<T>` does not yet support nullability for `T`. For example, `[AttachedBindableProperty<string?>]` will generate a compiler error.

To generate an Attached Property for a nullable type, set `IsNullable` to `true`:

```csharp
[AttachedBindableProperty<int>("Number", IsNullable = true)] 
public partial class CustomButton : Button
{
}
```

Setting `IsNullable = true` will generate an Attached Property using a nullable `T?`:

```csharp
public partial class CustomButton
{
    /// <summary>
    /// Attached BindableProperty for the Number property.
    /// </summary>
    public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Number", typeof(int), typeof(BindablePropertySourceGeneratorSample.CustomButton), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null);
    /// <summary>
    /// Gets Number for the <paramref name = "bindable"/> child element.
    /// </summary>
    public static int? GetNumber(global::Microsoft.Maui.Controls.BindableObject bindable) => (int? )bindable.GetValue(NumberProperty);
    /// <summary>
    /// Sets Number for the <paramref name = "bindable"/> child element.
    /// </summary>
    public static void SetNumber(global::Microsoft.Maui.Controls.BindableObject bindable, int? value) => bindable.SetValue(NumberProperty, value);
}
```

### Setting Default Value

To set the default value for `[AttachedBindableProperty<T>]`, assign a compile-time constant to the attribute's `DefaultValue` property:

```csharp
[AttachedBindableProperty<int>("Number", DefaultValue = 10)]
public partial class CustomButton : Button
{
}
```

### Setting Default `BindingMode`

To set the default `BindingMode` for `[BindableProperty]`, assign the value to the attribute's `DefaultBindingMode` property:

```csharp
[AttachedBindableProperty<int>("Number", DefaultBindingMode = BindingMode.TwoWay)]
public partial class CustomButton : Button
{
}
```

### Using `ValidateValue` 

To leverage the `ValidateValueDelegate`, use the `nameof()` operator to assign the name of the validation method to the the attribute's `ValidateValueMethodName` property:

```csharp
[AttachedBindableProperty<int>("Number", ValidateValueMethodName = nameof(ValidateNumber))]
public partial class CustomButton : Button
{
  static bool ValidateNumber(BindableObject bindable, object value)
  {
      var updatedValue = (int)value;
      return updatedValue > 0;
  }
}
```

> **Important:** The method used for `ValidateValueMethodName` must adhere to the [ValidateValueDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.validatevaluedelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static bool MethodName(BindableObject, object)`

### Using `PropertyChanging` 

To leverage the `BindingPropertyChangingDelegate`, use the `nameof()` operator to assign the name of the property changing method to the the attribute's `PropertyChangingMethodName` property:

```csharp
[AttachedBindableProperty<int>("Number", PropertyChangingMethodName = nameof(OnNumberChanging))]
public partial class CustomButton : Button
{
  static void OnNumberChanging(BindableObject bindable, object oldValue, object newValue)
  {
    Trace.WriteLine($"Old Value: {oldValue}");
    Trace.WriteLine($"New Value: {newValue}");
  }
}
```

> **Important:** The method used for `PropertyChangingMethodName` must adhere to the [BindingPropertyChangingDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.bindingpropertychangingdelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static void MethodName(BindableObject, object, object)`

### Using `PropertyChanged` 

To leverage the `BindingPropertyChangedDelegate`, use the `nameof()` operator to assign the name of the property changed method to the the attribute's `PropertyChangedMethodName` property:

```csharp
[AttachedBindableProperty<int>("Number", PropertyChangedMethodName = nameof(OnNumberChanged))]
public partial class CustomButton : Button
{
  static void OnNumberChanged(BindableObject bindable, object oldValue, object newValue)
  {
    Trace.WriteLine($"Old Value: {oldValue}");
    Trace.WriteLine($"New Value: {newValue}");
  }
}
```

> **Important:** The method used for `PropertyChangedMethodName` must adhere to the `BindingPropertyChangedDelegate` method signature: `static void MethodName(BindableObject, object, object)`

### Using `CoreceValue` 

To leverage the `CoerceValueDelegate`, use the `nameof()` operator to assign the name of the coerce value method to the the attribute's `CoerceValueMethodName` property:

```csharp
[AttachedBindableProperty<int>("Number", CoerceValueMethodName = nameof(CoerceNumber))]
public partial class CustomButton : Button
{
  static object CoerceNumber(BindableObject bindable, object value)
  {
      var updatedValue = (int)value;
      return Math.Clamp(updatedValue, 0, int.MaxValue);
  }
}
```

> **Important:** The method used for `CoerceValueMethodName` must adhere to the [CoreceValueDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.coercevaluedelegate?view=net-maui-9.0&ref=codetraveler.io) method signature: `static object MethodName(BindableObject, object)`

### Using `CreateDefaultValue`

To leverage the [CreateDefaultValueDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.createdefaultvaluedelegate-2?view=net-maui-9.0&ref=codetraveler.io), use the `nameof()` operator to assign the name of the create default value method to the the attribute's `DefaultValueCreatorMethodName` property:

```csharp
[AttachedBindableProperty<int>("Number", DefaultValueCreatorMethodName = nameof(CreateDefaultNumber))]
public partial class CustomButton : Button
{
    static object CreateDefaultNumber() => 10;
}
```

> **Important:** The method used for `DefaultValueCreatorMethodName` must adhere to the [CreateDefaultValueDelegate](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.createdefaultvaluedelegate-2?view=net-maui-9.0&ref=codetraveler.io) method signature: `static object MethodName()`

### Modifying Accessibility

By default, `[AttachedBindableProperty<T>]` will generate a `public` Bindable Property, a `public` Getter method and a `public` Setter method . To modify the accessibility of the Attached Property, set the value of the attribute's `BindablePropertyAccessibility`, `GetterAccessibility` and `SetterAccessibility` properties:

```csharp
[AttachedBindableProperty<int>("Number", BindablePropertyAccessibility = AccessModifier.Internal, GetterAccessibility = AccessModifier.Internal, SetterAccessibility = AccessModifier.Internal)]
public partial class CustomButton : Button
{
}
```

This example will generate the following code:

```csharp
public partial class CustomButton
{
    /// <summary>
    /// Attached BindableProperty for the Number property.
    /// </summary>
    internal static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.CreateAttached("Number", typeof(int), typeof(BindablePropertySourceGeneratorSample.CustomButton), null, (global::Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, null);
    /// <summary>
    /// Gets Number for the <paramref name = "bindable"/> child element.
    /// </summary>
    internal static int GetNumber(global::Microsoft.Maui.Controls.BindableObject bindable) => (int)bindable.GetValue(NumberProperty);
    /// <summary>
    /// Sets Number for the <paramref name = "bindable"/> child element.
    /// </summary>
    internal static void SetNumber(global::Microsoft.Maui.Controls.BindableObject bindable, int value) => bindable.SetValue(NumberProperty, value);
}
```

### Creating a Readonly Attached Property

The `[AttachedBindableProperty<T>]` attribute will automatically generate a Readonly Attached Bindable Property (eg [BindableProperty.CreateAttachedReadOnly()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.maui.controls.bindableproperty.createattachedreadonly?view=net-maui-10.0&ref=codetraveler.io)) when the `SetterAccessibility` value is set to `AccessModifier.Protected`, `AccessModifier.PrivateProtected` or `AccessModifier.Private`.

For example, a Readonly Attached Property will be generated for the following:

```csharp
[AttachedBindableProperty<int>("Number", SetterAccessibility = AccessModifier.Protected)]
[AttachedBindableProperty<char>("Letter", SetterAccessibility = AccessModifier.PrivateProtected)]
[AttachedBindableProperty<string>("Text", SetterAccessibility = AccessModifier.Private)]
public partial class CustomButton : Button
{
}
```

## Modifying XML Documentation

By default, `[AttachedBindableProperty<T>]` will generate the boiler-plate XML Documentation seen in examples above. To customize the XML Documentation generated, pass in the XML Documentation as a `string` to the attribute's `BindablePropertyXmlDocumentation` property, `GetterMethodXmlDocumentation` property and `SetterMethodXmlDocumentation` property accordingly:

```csharp
[AttachedBindableProperty<int>("Number", 
    BindablePropertyXmlDocumentation = NumberBindablePropertyXmlDocumentation, 
    GetterMethodXmlDocumentation = NumberGetterXmlDocumentation, 
    SetterMethodXmlDocumentation = NumberSetterXmlDocumentation)]
public partial class CustomButton : Button
{
    const string NumberBindablePropertyXmlDocumentation =
        """
        ///<Summary>The Bindable Property Number for a <see cref="CustomButton"/></Summary> 
        """;

    const string NumberGetterXmlDocumentation =
        """
        ///<Summary>Sets the value for a <see cref="CustomButton"/></Summary>
        """;

    const string NumberSetterXmlDocumentation =
        """
        ///<Summary>Gets the value for a <see cref="CustomButton"/></Summary>
        ///<Remarks>Number cannot be less than zero</Remarks> 
        """;
}
```

> **Important:** *The `string` used for the XML Documentation must contain both `///`* 

# Conclusion

I hope you enjoy using these new source generators! They were a ton of work to create, but I'm excited to see these bring a giant productivity boost to all developers in the .NET MAUI Community.

Please give us your feedback by opening a [Discussion on the .NET MAUI Community Toolkit GitHub repository](https://github.com/CommunityToolkit/Maui/discussions?ref=codetraveler.io). 

To see more examples of \`\[BindableProperty\] and `[AttachedBindableProperty<T>]`, check out the source code for [CommunityToolkit.Maui](https://github.com/CommunityToolkit/Maui/tree/main/src/CommunityToolkit.Maui?ref=codetraveler.io) to see how we're now using it to generate all of the Bindable Properties in our library.