Creating Custom ValueValidators

This tutorial will show you how to create custom ValueValidators by implementing a complete example of a ForbiddenWordValidator.

ValueValidators target all values of the specified type, regardless of where they are used. They are great if you want to validate non-unity objects, such as strings, structs, or classes. Let's go trough an example to see how they work.

If you're already familiar with creating other validator types, just look at the sample code and see what's different. All validator types have very similar code and the tutorials for each differ only slightly.

First we create the validator class and add the required boilerplate code. Alternatively, you can also create a new validator by right-clicking in the project folder and navigating to Odin Validator > Create Validator > Value Validator. This will create a new file for you and automatically add the boilerplate code. Odin tries to predict the correct code by looking at the name you chose for the validator, but you may have to change it a bit to make it work. You should now have something that looks similar to this code:

using Sirenix.OdinInspector.Editor.Validation;

[assembly: RegisterValidator(typeof(ForbiddenWordValidator))]

public class ForbiddenWordValidator : ValueValidator<string>
{
    protected override void Validate(ValidationResult result)
    {
    }
}
Let's go through it and explain what we're doing.
using Sirenix.OdinInspector.Editor.Validation;

The ValueValidator type is in the Sirenix.OdinInspector.Editor.Validation namespace, so we need to include it.

[assembly: RegisterValidator(typeof(ForbiddenWordValidator))]

We register the validator with the RegisterValidator attribute. There is another attribute that can be used to register a validator called RegisterValidationRule. The rest of the code is identical no matter which one you use, but registering a validator as a validation rule can give you additional functionality. To learn more about it, check out the following tutorial. Validators vs Validation Rules

Do not forget to register the validator, or it will not be picked up by the validation system.

public class ForbiddenWordValidator : ValueValidator<string>
{
    protected override void Validate(ValidationResult result)
    {
    }
}

We create the Validator class and inherit from ValueValidator<T>. Since we want to validate all strings, we need to pass string as a type parameter. You can also see that we're overriding the Validate method. This method is the bread and butter of any validator and is responsible for executing the validation logic and setting the appropriate validation result.

Let's implement the actuall validation logic now.

using System.Collections.Generic;
using Sirenix.OdinInspector.Editor.Validation;

[assembly: RegisterValidator(typeof(ForbiddenWordValidator))]

public class ForbiddenWordValidator : ValueValidator<string>
{
    private List<string> forbiddenWords = new List<string>
    {
        "Equilibrium",
        "Phosphorous",
        "Serendipity",
    };

    protected override void Validate(ValidationResult result)
    {
        if (forbiddenWords.Contains(this.Value))
        {
            result.AddWarning($"'{this.Value}' is a forbidden word, please change it!");
        }
    }
}

Let's take a look at the changes made to the boilerplate code.

using System.Collections.Generic;

We've added a new using directive in order to be able to use lists.

private List<string> forbiddenWords = new List<string>
{
    "Equilibrium",
    "Phosphorous",
    "Serendipity",
};

We've added a list field to the validator that contains all forbidden words.

protected override void Validate(ValidationResult result)
{
    if (forbiddenWords.Contains(this.Value))
    {
        result.AddWarning($"'{this.Value}' is a forbidden word, please change it!");
    }
}

This is where the actual validation logic is executed. First we check if the list of forbidden words contains this.Value and if it does, we add a warning to the validation results. this.Value refers to the currently validated value, in our case that's the string.

The result object is your main way of interacting with the validation system. Whenever your validator finds an issue you can add a warning / error to it using

  • .AddWarning()
  • .AddError()

Alternatively, you can also use the .Add() method, which takes a ValidatorSeverity argument. This is useful for validation rules (see Validators vs Validation Rules) as it allows you to configure the severity of this validator inside of the editor.

You can also add Buttons, Fixes, Metadata, and Context Menu Items by calling these methods after you have added warning / error.

  • .WithFix() (see Creating Custom Fixes)
  • .WithButton()
  • .WithMetaData()
  • .WithContextClick()
  • .WithSceneGUI()
  • .WithSelectionObject()
  • .EnableRichText()

If you want to draw something in the scene as the result of a warning / error you can do so by using the .WithSceneGUI() method.
All of these method calls can be chained since they use a builder pattern.

That's pretty much all you need to create a custom ValueValidator.
The validation logic can of course be a lot more sophisticated, but the general workflow is always the same.

Validators vs Validation Rules

You already know how to create custom validators and are interested in the difference between Validators and Validation Rules? Then this tutorial is right for you.

Validators vs Validation Rules

Custom Fixes

You want to add a button to your validator that executes a custom fix? Then this tutorial is right for you.

Creating Custom Fixes

Other Validator Types

Are you interested in the other validator types? Check out this overview to see which one is right for your use case.

Validator Types Overview