【Unity】Unity Attribute Guide: Metadata Markers That Give Your Code Special Behaviors

Created: 2026-02-05

Learn how to customize Unity's Inspector using C# Attributes. Covers practical usage of SerializeField, Header, Range, RequireComponent, and more.

Overview

Tested with: Unity 2022.3 LTS / Unity 6

"I want to display private variables in the Inspector, but how?" "I want to add headings above fields to make the Inspector easier to read..."

This is where Attributes come in. Attributes are C# metadata markers that, when placed above class, property, or method declarations, instruct special behaviors.

[HideInInspector]
public float strength;

Inspector Attributes

SerializeField: Display Private Variables in the Inspector

[SerializeField]
private float speed = 5.0f;

This allows designers and planners to adjust values in the Inspector while maintaining encapsulation.

field: SerializeField: Serialize Properties

A modern way to display C# auto-properties in the Inspector.

[field: SerializeField] public float Speed { get; private set; } = 5.0f;

This provides a clean way to make a property read-only from code but editable in the Inspector.

HideInInspector: Hide Variables from the Inspector

[HideInInspector]
public float currentHealth;

The value is still serialized, so it is saved.

NonSerialized: Disable Serialization

[System.NonSerialized]
public float tempValue;
AttributeSerializedShown in Inspector
HideInInspectorYesNo
NonSerializedNoNo

When to use which: Use NonSerialized for temporary variables you don't want saved. Use HideInInspector when you want the value saved but not editable in the Inspector.

Header: Display a Heading

[Header("Movement Settings")]
[SerializeField] private float moveSpeed = 5.0f;
[SerializeField] private float jumpForce = 10.0f;

[Header("Attack Settings")]
[SerializeField] private float attackPower = 20.0f;

Tooltip: Display a Tooltip

[Tooltip("Movement speed (units: m/s)")]
[SerializeField] private float moveSpeed = 5.0f;

Range: Display a Slider

[Range(0, 100)]
[SerializeField] private float volume = 50.0f;

Note: Range only restricts input in the Inspector. When assigning values directly from script, out-of-range values can still be set. Use Mathf.Clamp alongside if you need to enforce limits in code as well.

Min: Set a Minimum Value

[Min(0)]
[SerializeField] private float health = 100.0f;

Space: Add Spacing

[SerializeField] private float moveSpeed = 5.0f;
[Space(20)]
[SerializeField] private float attackPower = 20.0f;

TextArea: Multi-line Text Input

[TextArea(3, 10)]
[SerializeField] private string description;

Method Attributes

ContextMenu: Add Methods to the Inspector Context Menu

[ContextMenu("Restore Full Health")]
private void ResetHealth()
{
    currentHealth = maxHealth;
    Debug.Log("Health fully restored");
}

Right-click the component in the Inspector to see the menu item.

MenuItem: Add Methods to the Unity Editor Menu

Important: Scripts using [MenuItem] must be placed inside an Editor folder. The UnityEditor namespace is editor-only, so placing them outside the Editor folder will cause build errors.

// Editor/MyEditorTools.cs
using UnityEditor;
using UnityEngine;

public class MyEditorTools
{
    [MenuItem("Tools/Reset All Objects")]
    private static void ResetAllObjects()
    {
        Debug.Log("All objects have been reset");
    }
}

RuntimeInitializeOnLoadMethod: Initialize at Game Startup

using UnityEngine;

public class GameInitializer
{
    [RuntimeInitializeOnLoadMethod]
    private static void Initialize()
    {
        Debug.Log("Game has started");
    }
}

This can be used in static classes that do not inherit from MonoBehaviour.

Class Attributes

RequireComponent: Specify Required Components

[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour
{
    private Rigidbody rb;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
}

When you attach PlayerController, a Rigidbody is automatically attached as well.

DisallowMultipleComponent: Prevent Duplicate Components

[DisallowMultipleComponent]
public class GameManager : MonoBehaviour
{
}

ExecuteAlways: Run in Editor Mode

[ExecuteAlways]
public class GridGenerator : MonoBehaviour
{
    void Update()
    {
        // Separate logic for editor mode and play mode
        if (Application.isPlaying)
        {
            // Runtime logic
        }
        else
        {
            // Editor logic (e.g., visualizing in Scene view)
        }
    }
}

Note: With [ExecuteAlways], Update is called every frame even in editor mode. Heavy operations will slow down the editor, so use Application.isPlaying to branch logic or keep operations lightweight.

CreateAssetMenu: Add a ScriptableObject Creation Menu

[CreateAssetMenu(fileName = "NewWeapon", menuName = "MyGame/Weapon")]
public class WeaponData : ScriptableObject
{
    public string weaponName;
    public int damage;
}

Serialization Attributes

Serializable: Serialize Custom Classes

To display custom classes (that don't inherit from MonoBehaviour or ScriptableObject) in the Inspector, you need [Serializable].

[System.Serializable]
public class EnemyData
{
    public string name;
    public int hp;
    public float speed;
}

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private EnemyData[] enemies;
}

SerializeReference: Polymorphic Serialization

Allows you to hold instances of derived classes in base class or interface fields.

public interface ISkill
{
    void Execute();
}

[System.Serializable]
public class FireSkill : ISkill
{
    public float damage;
    public void Execute() { /* Fire attack */ }
}

[System.Serializable]
public class IceSkill : ISkill
{
    public float slowDuration;
    public void Execute() { /* Ice attack */ }
}

public class Player : MonoBehaviour
{
    [SerializeReference] private ISkill skill;  // Can hold FireSkill or IceSkill
}

Note: When using SerializeReference, selecting types in the Inspector requires a custom editor or a third-party asset like OdinInspector.

Custom Attributes

Defining a Custom Attribute

To create a custom Attribute that affects the Inspector, inherit from PropertyAttribute.

using UnityEngine;

[System.AttributeUsage(System.AttributeTargets.Field)]
public class ReadOnlyAttribute : PropertyAttribute
{
}

Note: You must inherit from UnityEngine.PropertyAttribute, not System.Attribute. Inheriting from System.Attribute will not work with PropertyDrawers.

Combining with Editor Extensions

PropertyDrawers must be placed inside an Editor folder.

Assets/
├── Scripts/
│   └── ReadOnlyAttribute.cs    // Attribute definition
└── Editor/
    └── ReadOnlyDrawer.cs       // PropertyDrawer (inside Editor folder)
// Editor/ReadOnlyDrawer.cs
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        GUI.enabled = false;
        EditorGUI.PropertyField(position, property, label);
        GUI.enabled = true;
    }
}

Important: Placing PropertyDrawers outside the Editor folder will cause build errors. The UnityEditor namespace is editor-only and cannot be included in builds.

Practical Examples

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class PlayerController : MonoBehaviour
{
    [Header("Movement Settings")]
    [Tooltip("Movement speed (units: m/s)")]
    [SerializeField] private float moveSpeed = 5.0f;

    [Tooltip("Jump force")]
    [SerializeField] private float jumpForce = 10.0f;

    [Space(20)]

    [Header("Attack Settings")]
    [Range(10, 100)]
    [SerializeField] private float attackPower = 20.0f;

    [Min(0)]
    [SerializeField] private float attackRange = 2.0f;

    [Space(20)]

    [Header("Status")]
    [HideInInspector]
    public float currentHealth;

    [SerializeField] private float maxHealth = 100.0f;

    [ContextMenu("Restore Full Health")]
    private void ResetHealth()
    {
        currentHealth = maxHealth;
    }
}

Best Practices

  • Combine SerializeField with private - Maintain encapsulation while allowing Inspector adjustments
  • Use Header and Space for readability - Group variables to improve Inspector organization
  • Add Tooltip descriptions - Help team members understand each field
  • Set appropriate ranges with Range and Min - Prevent incorrect value assignments
  • Use RequireComponent for dependencies - Make component dependencies explicit

Summary

Attributes are C# metadata markers that instruct special behaviors in your code.

  • SerializeField - Display private variables in the Inspector
  • Header, Space - Improve Inspector readability
  • Range, Min - Set value range restrictions
  • RequireComponent - Auto-attach required components
  • ContextMenu - Make methods executable from the editor

Use Attributes to write more readable and maintainable code.

Further Learning