Table of Contents Show
One of the most useful features introduced with Unity UI Toolkit is data binding. This means that data is automatically synchronized between a data source and the UI.
In this tutorial, we’ll build a simple user profile page. The UI will automatically populate from a C# data source, and users will be able to edit their information directly in the interface.

Created with Unity 6.3 | Source code on Github
Set up the UI files
We’ll start by creating the profile interface in UXML. Look at this tutorial if you are not familiar yet with UXML and USS.
The page will contain an image, two text fields for the user’s name and email address, and a Save button.
Place all of these elements inside a parent VisualElement named profile.
We’ll use the BEM naming convention for the UI elements. These names are important because we’ll use them later to find the elements from C#.
The structure will be:
profile
├── profile__image
├── profile__input-name
├── profile__input-mail
└── profile__button-save
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:VisualElement name="profile" style="flex-grow: 1;">
<ui:Image name="profile__image"/>
<ui:TextField label="Name" placeholder-text="Enter your name" name="profile__input-name"/>
<ui:TextField label="E-Mail" placeholder-text="Enter your e-mail address" name="profile__input-mail"/>
<ui:Button text="Save" name="profile__button-save"/>
</ui:VisualElement>
</ui:UXML> The visual styling isn’t important for this tutorial, so we won’t cover the USS here. If you want to achieve the same look as in the picture, download the USS file from the GitHub project and add it to the UXML file.
Create the data
Any C# object can serve as a data source, including ScriptableObject, MonoBehaviour, regular C# classes, and structs.
For this example, we’ll use a plain C# class called UserData. In a real application, the data could come from a database, web API, or another external source.
Create a new class named UserData. Mark the class with [Serializable] to conveniently edit the data in the Unity editor. This is not necessary for the data binding.
[Serializable]
public class UserData
{
} Next, we’ll add properties for the user’s name, email address, and profile image.
[SerializeField, DontCreateProperty]
private string m_name;
[CreateProperty]
public string Name
{
get => m_name;
set => m_name = value;
} The [CreateProperty] attribute is what makes the property available to Unity’s data-binding system. Only properties marked with this attribute will appear in the UI Builder’s binding window.
The private member variable is a serialized field, for which Unity automatically creates a property. We add the [DontCreateProperty] attribute to it because we don’t want the backing field to appear as a separate property in the binding window.
Add the email and image properties in the same way.
[SerializeField, DontCreateProperty]
private string m_email;
[CreateProperty]
public string Email
{
get => m_email;
set => m_email = value;
}
[SerializeField, DontCreateProperty]
private Texture2D m_image;
[CreateProperty]
public Texture2D Image
{
get => m_image;
set => m_image = value;
} Add the Data Bindings
Now we need to tell UI Toolkit which data type is associated with our profile page UI.
Open the UXML file with the UI Builder, select the profile container, and go to Bindings → Data Source. Enable Type, select Select Type…, and choose UserData.


The entire profile container now uses UserData as its data source type.
Next, we’ll connect individual UI elements to properties of UserData.
Select the name field, right-click the Value property, and choose Add Binding.

This opens the binding window. Click into the Data Source Path, and you will see a pop-up with all properties of the UserData class. Select Name from the list and click on Add Binding.

Repeat the process for the email field.
For the image, create a binding on its Background/Image property and select Image as Data Source Path.
At this point, the UI knows which properties it should display—but we still need to provide an actual UserData object at runtime.
Provide the Data at Runtime
Create a UserProfileTutorial MonoBehaviour and attach it to the same GameObject as the UIDocument. The script retrieves the container named profile and assigns an object of type UserData as its data source.
public class UserProfileTutorial : MonoBehaviour
{
[SerializeField]
private UserData m_userData;
void Start()
{
var uiDocument = GetComponent<UIDocument>();
var profile = uiDocument.rootVisualElement.Q<VisualElement>("profile");
profile.dataSource = GetUserData();
}
private UserData GetUserData()
{
// replace this with real data from a database
return m_userData;
}
} For this tutorial, we’ll use a serialized UserData field so that we can enter test values directly in the Unity Inspector.
When you enter Play Mode, the profile should now display those values automatically.
Why don’t Inspector changes update the UI?
Maybe you tried to edit the values in the inspector at runtime and observed they didn’t change in the UI.
There’s an important detail here. The binding system is listening to the public properties:
userData.Name
userata.Email
userData.Image
On the other hand, the inspector changes the serialized private members. This doesn’t trigger the property-based notifications that data binding relies on.
You can verify that the binding works by changing the property from code:
void TestNameChange()
{
m_userData.Name = "Test";
} For example:
void Start()
{
// Existing code ...
Invoke(nameof(TestNameChange), 1f);
} After one second, the name displayed in the UI should change automatically.
Enable Two-Way Binding
By default, our bindings are To Target. In other words, data flows only from the data source into the UI.
That’s perfect for displaying information, but it doesn’t allow the user to edit the underlying data.
To enable editing, right-click on the name and email bindings in UI Builder and choose Edit Binding….
Change the Binding Mode to Two Way. With two-way binding enabled, changes can flow in both directions. From the UserData object to the UI, and also from the UI back to the UserData object.

Now, when the user edits the name or email field, the corresponding property in the data object is updated automatically.
In play mode, you can observe the UserData values in the inspector.

Save changes only when requested
At the moment, two-way binding updates the data immediately. But this isn’t always desirable.
Typically, you’d want users to be able to make several changes and then either Save or discard them.
To achieve this, we’ll create a copy of the original UserData object.
The UI will work with the copy, while the original data remains unchanged until the user presses Save.
First, add a constructor and copy constructor to UserData:
public UserData(string name = "", string email = "", Texture2D image = null)
{
m_name = name;
m_email = email;
m_image = image;
}
public UserData(UserData other)
{
m_name = other.m_name;
m_email = other.m_email;
m_image = other.m_image;
}
We’ll also add a method for copying the edited values back to the original object:
public void Refresh(UserData other)
{
m_name = other.m_name;
m_email = other.m_email;
m_image = other.m_image;
} Now add a second UserData field to UserProfileTutorial:
private UserData m_userDataCopy; Create the copy and use it as the UI’s data source:
m_userDataCopy = new UserData(GetUserData());
profile.dataSource = m_userDataCopy; Implement the Save Button
Finally, retrieve the Save button and register a click callback:
private void Start()
{
// ... existing code ...
var saveButton = uiDocument.rootVisualElement.Q<Button>("profile__button-save");
saveButton.RegisterCallback<ClickEvent>(OnSave);
}
The callback copies the edited data back to the original object:
private void OnSave(ClickEvent _)
{
m_userData.Refresh(m_userDataCopy);
} Enable the Save Button Only When Necessary
There’s one final improvement we can make.
A Save button usually shouldn’t be active when there are no unsaved changes. We can implement this using data binding as well.
Add a Changed property to UserData. It’s not serialized, but it has the [CreateProperty] attribute.
private bool m_changed;
[CreateProperty]
public bool Changed
{
get => m_changed;
set => m_changed = value;
}
Now update the other properties so that they mark the object as changed whenever a value is modified:
public string Name
{
get => m_name;
set
{
m_name = value;
Changed = true;
}
} Do the same for Email and Image.
When the user saves the profile, reset the flag:
private void OnSave(ClickEvent _)
{
m_userData.Refresh(m_userDataCopy);
m_userDataCopy.Changed = false;
}
Finally, return to the UI Builder, select the Save button, and bind the Enabled property to Changed. A To Target binding is sufficient here because we only need the Changed value to control the button.

The result is a clean data-binding workflow where the UI automatically reflects the data, user edits are kept separate until saved, and the Save button is enabled only when there are actual changes.
