Version: Unity 6.5 Alpha (6000.5)
Language : English
Serialization rules
Custom serialization

Serialization rules analyzer reference

Unity has a serialization Roslyn diagnostic analyzer that helps you write correct serialization code by detecting common mistakes in using Unity’s serialization system at compile time. It provides real-time feedback in your IDE to prevent serialization issues before they cause runtime problems.

For detailed information about Unity’s serialization rules, refer to Script Serialization Rules.

Analyzer rules

The serialization analyzer implements the following diagnostic rules:

For information about suppressing rule warnings, refer to Suppressing warnings.

UAC1000: SerializeReference field type missing the Serializable attribute

Severity: Warning.

When a field is serialized by [SerializeReference], its type should have the [Serializable] attribute. If the type is missing [Serializable], Unity issues a warning. The field is serialized via [SerializeReference] even though the type isn’t considered serializable in other serialization contexts, which can lead to inconsistent behavior. To avoid the issue, add [Serializable] to the class or use the [MakeSerializable] attribute.

Marking the type with [Serializable] (or [MakeSerializable] when you can’t modify the type) makes the intent explicit and keeps behavior consistent across Unity’s serialization paths. Without it, a field with [SerializeReference] still persists the data, while other forms of serialization that rely on the type being serializable can skip it, leading to inconsistent results.

Incorrect example: Missing [Serializable] on a class used with [SerializeReference]:

// INCORRECT: Missing [Serializable] on CustomData
public class CustomData
{
    public int value;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public CustomData data; // UAC1000: CustomData lacks [Serializable]
}

Correct example: Add [Serializable] to the class:

// CORRECT: Add [Serializable] to the class
[System.Serializable]
public class CustomData
{
    public int value;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public CustomData data; // OK
}

UAC1001: Public field skipped by serialization due to missing the Serializable attribute

Severity: Warning.

Unity considers public fields of custom classes as candidates for serialization. Without the [Serializable] attribute on a type, Unity does not serialize fields of this type, and you can lose data when entering Play mode or when the sceneA Scene contains the environments and menus of your game. Think of each unique Scene file as a unique level. In each Scene, you place your environments, obstacles, and decorations, essentially designing and building your game in pieces. More info
See in Glossary
is saved.

To fix this issue, you have multiple options depending on your intent:

  • Add [Serializable] to the field’s type (if serialization is needed).
  • Make the field private (if serialization isn’t needed).
  • Add [System.NonSerialized] to the field (to explicitly mark it as non-serialized).
  • Convert the field into a property (Unity doesn’t serialize properties), if you only need a public API surface.

Without the [Serializable] attribute, Unity skips the field during serialization, causing data loss. This is problematic because the field compiles successfully but loses its value during serialization and deserialization cycles.

Incorrect example: Missing [Serializable] on a class used as a serialized field:

// INCORRECT: Missing [Serializable] on PlayerStats
public class PlayerStats
{
    public int health;
    public int mana;
}

public class Player : MonoBehaviour
{
    public PlayerStats stats; // UAC1001: PlayerStats lacks [Serializable]
}

Correct example: Add [Serializable] to the class:

// CORRECT: Add [Serializable] to the class
[System.Serializable]
public class PlayerStats
{
    public int health;
    public int mana;
}

public class Player : MonoBehaviour
{
    public PlayerStats stats; // OK - will be serialized inline
}

UAC1002: Inheritance hierarchy incomplete serialization

Severity: Warning.

Every class in a serialization hierarchy must be explicitly marked with [Serializable], because the attribute isn’t inherited from a base class to its derived classes. If any class in the hierarchy is missing [Serializable], Unity might not serialize the fields declared on that class, which can result in unexpected data loss when entering Play mode, saving scenes, or performing domain reloads.

If you can’t modify a base class (for example, it comes from a third-party library or a precompiled assembly), use the [MakeSerializable] assembly-level attribute to mark it as serializable without changing its source code. Fixing this warning also enables the analyzer to check the base class’s own fields. Refer to Limitation: base class field analysis for more details.

Incorrect example: Inheritance hierarchy missing [Serializable] on a base class:

// INCORRECT: Missing [Serializable] on base class
public class BaseEntity
{
    public string name;
}

[System.Serializable]
public class Enemy : BaseEntity  // UAC1002: Enemy's hierarchy incomplete (BaseEntity lacks [Serializable])
{
    public int damage;
}

Correct example: Mark each class in the hierarchy with [Serializable]:

// CORRECT: Mark each class in the hierarchy with [Serializable]
[System.Serializable]
public class BaseEntity
{
    public string name;
}

[System.Serializable]
public class Enemy : BaseEntity
{
    public int damage;
}

public class GameManager : MonoBehaviour
{
    public Enemy enemy; // OK - entire hierarchy is serializable
}

Alternative correct example: Use [MakeSerializable] for inaccessible base classes:

If you can’t modify the base class (for example, it comes from a precompiled assembly or third-party library), you can use the [MakeSerializable] assembly attribute:

// CORRECT: Use [MakeSerializable] for inaccessible base classes
[assembly: UnityEngine.Serialization.MakeSerializable(typeof(ThirdPartyLibrary.BaseEntity))]

namespace ThirdPartyLibrary
{
    // This class is from a precompiled assembly - you can't modify it
    public class BaseEntity
    {
        public string name;
    }
}

namespace YourGame
{
    [System.Serializable]
    public class Enemy : ThirdPartyLibrary.BaseEntity
    {
        public int damage;
    }

    public class GameManager : MonoBehaviour
    {
        public Enemy enemy; // OK - BaseEntity marked serializable via [MakeSerializable]
    }
}

UAC1003: [SerializeReference] used on struct

Severity: Error.

The [SerializeReference] attribute can only be used on reference types (classes). It enables reference semantics such as null support, polymorphism, and shared references, which are incompatible with value types. Structs are value types and are always serialized inline, so using [SerializeReference] on a struct causes a runtime error.

Incorrect example: Using [SerializeReference] on a struct:

// INCORRECT: Using [SerializeReference] on a struct
[System.Serializable]
public struct Vector3D
{
    public float x, y, z;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public Vector3D position; // UAC1003: Error - can't use [SerializeReference] on struct
}

Correct example: Remove [SerializeReference] from the struct field:

// CORRECT: Remove [SerializeReference] from the struct field
[System.Serializable]
public struct Vector3D
{
    public float x, y, z;
}

public class MyBehaviour : MonoBehaviour
{
    public Vector3D position; // OK - structs are always serialized inline
}

Alternative correct example: Use a class:

// CORRECT: Use a class instead of a struct
[System.Serializable]
public class Vector3D
{
    public float x, y, z;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public Vector3D position; // OK - class can use [SerializeReference]
}

UAC1004: [SerializeReference] used on primitive or enum type

Severity: Warning.

Built-in types (int, float, bool, etc.) and enums are value types, so they are always serialized inline. Unity silently ignores [SerializeReference] on these types instead of producing a runtime error. The behavior is different for structs, where Unity raises errors which are caught by UAC1003). Removing the unnecessary [SerializeReference] makes the serialization intent of the field clearer.

Incorrect example: Using [SerializeReference] on a primitive or enum:

// INCORRECT: Using [SerializeReference] on a primitive and an enum
public enum GameState
{
    Menu,
    Playing,
    Paused
}

public class GameController : MonoBehaviour
{
    [SerializeReference] public int score; // UAC1004: int is a primitive type
    [SerializeReference] public GameState state; // UAC1004: GameState is an enum
}

Correct example: Remove [SerializeReference] from primitive and enum fields:

// CORRECT: Remove [SerializeReference] from primitive and enum fields
public enum GameState
{
    Menu,
    Playing,
    Paused
}

public class GameController : MonoBehaviour
{
    public int score; // OK - primitives don't need [SerializeReference]
    public GameState state; // OK - enums don't need [SerializeReference]
}

UAC1005: Direct self-reference cycle in serialized class

Severity: Warning.

When a class contains a field of its own type, it creates a direct self-reference cycle. Unity’s inline serialization copies object graphs by value, which can’t represent cycles. To avoid infinite loops, Unity ignores any field of a type that contains a direct self-reference cycle. This causes data loss that can be difficult to debug, because the field compiles without errors but is always null after deserialization. UAC1006 complements this rule by reporting the same problem at the field’s usage location rather than the type declaration.

There are two common, valid ways to represent self-reference data:

  • Use [SerializeReference] on the link field (supports cycles and reference semantics).
  • Use a collection (for example, List<Node> or Node[]) to represent children or links. Collections can be empty and the object graph can remain cycle-free, but cycles (including adding this) still can’t be represented correctly without the [SerializeReference] attribute.

Incorrect example: Direct self-reference cycle in an inline-serialized class:

// INCORRECT: Direct self-reference cycle
[System.Serializable]
public class Node
{
    public string data;
    public Node next; // UAC1005: Direct self-reference - Unity will ignore this field
}

public class LinkedList : MonoBehaviour
{
    public Node head; // After serialization, head will always be null
}

Correct example: Use [SerializeReference] for self-referential links:

// CORRECT: Use [SerializeReference] for self-referential links
[System.Serializable]
public class Node
{
    public string data;
    [SerializeReference] public Node next; // OK - [SerializeReference] supports cycles
}

public class LinkedList : MonoBehaviour
{
    [SerializeReference] public Node head; // OK - preserves linked structure
}

Alternative correct example: Redesign to avoid cycles:

// CORRECT: Redesign to avoid cycle
[System.Serializable]
public class NodeData
{
    public string data;
    public int nextIndex; // Store index instead of direct reference
}

public class LinkedList : MonoBehaviour
{
    public NodeData[] nodes; // OK - no direct cycles
}

UAC1006: Field uses type with direct self-reference cycle

Severity: Warning.

When you use a type that contains a direct self-reference cycle, Unity ignores that field during serialization. This warning appears at the usage location so you can notice data loss even when the problematic type is declared elsewhere. Refer to UAC1005 for details on what causes a direct self-reference cycle and how to fix it.

Incorrect example: Field uses a type with a direct self-reference cycle:

// INCORRECT: Field uses type with direct self-reference cycle
[System.Serializable]
public class TreeNode
{
    public string value;
    public TreeNode left; // Direct self-reference
    public TreeNode right; // Direct self-reference
}

public class Tree : MonoBehaviour
{
    public TreeNode root; // UAC1006: TreeNode contains self-reference cycles
    // After serialization, the root field will be ignored and remain null
}

Correct example: Use [SerializeReference] for cyclic references:

// CORRECT: Use [SerializeReference] for cyclic references
[System.Serializable]
public class TreeNode
{
    public string value;
    [SerializeReference] public TreeNode left; // Fixed with [SerializeReference]
    [SerializeReference] public TreeNode right; // Fixed with [SerializeReference]
}

public class Tree : MonoBehaviour
{
    [SerializeReference] public TreeNode root; // OK - preserves tree structure
}

UAC1007: Deep self-reference cycle in serialized class

Severity: Warning.

When a class contains fields that create an indirect cycle (A > B > A or A > B > C > A), Unity still serializes the structure, but limits recursion to 10 levels. This is different from direct cycles, which are ignored entirely. After 10 levels Unity stops and discards the remaining data, which can cause silent data loss in deeply nested trees or graphs. This warning is reported on the field that participates in the cycle. UAC1008 reports the same problem at the usage location.

Incorrect example: Deep self-reference cycle with inline serialization:

// INCORRECT: Deep self-reference cycle with inline serialization
[System.Serializable]
public class Parent
{
    public string name;
    public Child child; // UAC1007: Creates deep cycle (Parent → Child → Parent)
}

[System.Serializable]
public class Child
{
    public string name;
    public Parent parent; // Creates the cycle back to Parent
}

public class Family : MonoBehaviour
{
    public Parent root; // Will serialize up to 10 levels: Parent→Child→Parent→Child...
}

Correct example: Use [SerializeReference] for deep cycles:

// CORRECT: Use [SerializeReference] for deep cycles
[System.Serializable]
public class Parent
{
    public string name;
    [SerializeReference] public Child child; // OK - [SerializeReference] handles cycles
}

[System.Serializable]
public class Child
{
    public string name;
    [SerializeReference] public Parent parent; // OK - preserves bidirectional relationship
}

public class Family : MonoBehaviour
{
    [SerializeReference] public Parent root; // OK - no depth limit
}

Alternative correct example: Redesign to avoid cycles:

// CORRECT: Redesign to avoid cycles
[System.Serializable]
public class Parent
{
    public string name;
    public List<string> childNames; // Store references differently
}

[System.Serializable]
public class Child
{
    public string name;
    public string parentName; // Use name lookup instead of direct reference
}

public class Family : MonoBehaviour
{
    public List<Parent> parents; // OK - no cycles
    public List<Child> children; // OK - no cycles
}

UAC1008: Field uses type with deep self-reference cycle

Severity: Warning.

When you use a type that contains a deep self-reference cycle, Unity serializes it with a 10-level recursion limit, which can cause silent data loss in deeply nested instances. This warning appears at the usage location so you can catch the issue even when the problematic type is declared elsewhere. Refer to UAC1007 for details on what causes a deep cycle and how to fix it.

Incorrect example: Field uses a type with a deep self-reference cycle:

// INCORRECT: Field uses type with deep self-reference cycle
[System.Serializable]
public class A
{
    public B b; // Creates deep cycle: A -> B -> A
}

[System.Serializable]
public class B
{
    public A a; // Completes the cycle
}

public class Host : MonoBehaviour
{
    public A root; // UAC1008: A contains a deep self-reference cycle
}

Correct example: Use [SerializeReference] to avoid depth limits:

// CORRECT: Use [SerializeReference] to avoid depth limits
[System.Serializable]
public class GraphNode
{
    public string id;
    [SerializeReference] public List<GraphNode> connections; // OK - no depth limit
}

public class Graph : MonoBehaviour
{
    [SerializeReference] public List<GraphNode> nodes; // OK - handles arbitrary depth
}

Alternative correct example: Use IDs:

// CORRECT: Use IDs instead of direct references
[System.Serializable]
public class GraphNode
{
    public string id;
    public List<string> connectionIds; // Store IDs instead of references
}

public class Graph : MonoBehaviour
{
    public List<GraphNode> nodes; // OK - no cycles, resolve IDs at runtime
    
    public GraphNode FindNode(string id)
    {
        return nodes.Find(n => n.id == id);
    }
}

UAC1009: Unsupported collection type for serialization

Severity: Warning.

Unity’s serialization system only supports two collection types: single-dimensional arrays (T[]) and List<T>. Fields that use unsupported collection types aren’t serialized, which can cause data loss because the field compiles and works at runtime but loses its value after a serialization cycle (for example, when saving a scene or reloading the domain).

Unsupported collection types include:

  • Multidimensional arrays (for example, int[,], int[,,]).
  • Jagged arrays (for example, int[][]).
  • Nested lists (for example, List<List<T>>).
  • Dictionary<TKey, TValue>, HashSet<T>, Queue<T>, Stack<T>, and other collections from System.Collections.Generic.

Incorrect example: Using unsupported collection types for serialization:

// INCORRECT: Using unsupported collection types
public class InventorySystem : MonoBehaviour
{
    public int[,] grid; // UAC1009: Multidimensional array not supported
    public int[][] jaggedArray; // UAC1009: Jagged array not supported
    public Dictionary<string, int> itemCounts; // UAC1009: Dictionary not supported
    public HashSet<string> unlockedAchievements; // UAC1009: HashSet not supported
    public Queue<string> messageQueue; // UAC1009: Queue not supported
    public Stack<string> undoStack; // UAC1009: Stack not supported
}

Correct example: Mark runtime-only collections as [NonSerialized]:

If you don’t need these fields to persist (for example, they are runtime-only data structures), you can either make them non-serialized (for example by making the field non-public) or mark them with [System.NonSerialized] if they must remain a public field. This acknowledges they won’t be serialized and suppresses the warning:

// CORRECT: Mark runtime-only collections as [NonSerialized]
public class InventorySystem : MonoBehaviour
{
    [System.NonSerialized] public Dictionary<string, int> itemCounts; // OK - explicitly marked as non-serialized
    [System.NonSerialized] public HashSet<string> unlockedAchievements; // OK - runtime-only data
    [System.NonSerialized] public Queue<string> messageQueue; // OK - transient queue
    [System.NonSerialized] public Stack<string> undoStack; // OK - runtime undo stack
    
    private void Awake()
    {
        // Initialize runtime collections
        itemCounts = new Dictionary<string, int>();
        unlockedAchievements = new HashSet<string>();
        messageQueue = new Queue<string>();
        undoStack = new Stack<string>();
    }
}

Alternative correct example: Use supported collection types:

If you need the data to persist, convert to supported collection types:

// CORRECT: Use supported collection types
[System.Serializable]
public struct KeyValuePair
{
    public string key;
    public int value;
}

public class InventorySystem : MonoBehaviour
{
    public List<KeyValuePair> itemCounts; // OK - List is supported
    public List<string> unlockedAchievements; // OK - List instead of HashSet
    public List<string> messageQueue; // OK - List instead of Queue
    public List<string> undoStack; // OK - List instead of Stack
    
    // Helper methods to maintain collection semantics
    public int GetItemCount(string itemName)
    {
        var pair = itemCounts.Find(p => p.key == itemName);
        return pair.value;
    }
    
    public void SetItemCount(string itemName, int count)
    {
        int index = itemCounts.FindIndex(p => p.key == itemName);
        if (index >= 0)
            itemCounts[index] = new KeyValuePair { key = itemName, value = count };
        else
            itemCounts.Add(new KeyValuePair { key = itemName, value = count });
    }
}

Alternative correct example: Use a custom serializable wrapper with ISerializationCallbackReceiver:

If you need the full semantics of a HashSet or Dictionary while also keeping the data serialized, you can create a wrapper class that implements ISerializationCallbackReceiver. This allows you to store data in a Unity-supported format (arrays or List<T>) and convert to and from the runtime collection in the serialization callbacks:

// CORRECT: Use a custom serializable wrapper
[System.Serializable]
public class SerializableHashSet<T> : ISerializationCallbackReceiver
{
    [SerializeField] private List<T> items = new List<T>();
    private HashSet<T> hashSet;

    public void OnBeforeSerialize()
    {
        if (hashSet == null) hashSet = new HashSet<T>(items);
        items.Clear();
        items.AddRange(hashSet);
    }

    public void OnAfterDeserialize()
    {
        hashSet = new HashSet<T>(items);
    }

    // Add collection methods (Add, Remove, Contains, Count, etc.) as needed.
}

public class AchievementManager : MonoBehaviour
{
    public SerializableHashSet<string> unlockedAchievements; // OK - custom serializable wrapper
}

The same pattern works for Dictionary<TKey, TValue> using parallel key-value lists. For complete, ready-to-use implementations of SerializableHashSet<T> and SerializableDictionary<TKey, TValue>, refer to Custom serialization.

Note: UAC1009 also applies when unsupported collection types appear as elements inside a supported collection. For example, List<Dictionary<string, int>> and Dictionary<string, int>[] are both flagged because their elements can’t be serialized by Unity.

UAC1010: [SerializeField] used on non-serializable type

Severity: Warning.

Applying [SerializeField] to a field does not make its type serializable. The attribute instructs Unity to save the field, but if the field’s type is not serializable, Unity does not save the value and the field resets after a serialization cycle (for example, when reloading a scene, reloading the domain, or reopening the project).

To fix this warning, choose the option that matches your intent:

  • If serialization is needed, make the field’s type serializable. For example, add [Serializable], or use [MakeSerializable] if you can’t modify the type.
  • If serialization is not needed, remove [SerializeField], or make the field non-serialized in another way.
  • If the field must remain public but should not be serialized, add [System.NonSerialized] to make that explicit.

Incorrect example: Using [SerializeField] on a non-serializable type:

// INCORRECT: Using [SerializeField] on a non-serializable type
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    [SerializeField] private MyWindowState m_WindowState; // UAC1010: Unity won't persist this field
}

Correct example: Remove [SerializeField] when persistence isn’t needed:

If the field doesn’t need to be serialized (for example, it’s runtime-only state), remove [SerializeField]:

// CORRECT: Remove [SerializeField] when persistence isn't needed
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    private MyWindowState m_WindowState; // OK - not serialized
}

Correct example: Mark the field as [NonSerialized] when persistence isn’t needed:

If the field must remain public but shouldn’t be serialized, add [System.NonSerialized]:

// CORRECT: Mark the field as [NonSerialized]
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    [System.NonSerialized] public MyWindowState windowState; // OK - explicitly not serialized
}

Correct example: Make the type [Serializable] when persistence is needed:

If you need the field to be serialized, make the type [Serializable]:

// CORRECT: Make the type [Serializable]
[System.Serializable]
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    [SerializeField] private MyWindowState m_WindowState; // OK - type is now serializable
}

UAC1011: Enum type exceeds 32-bit size limit

Severity: Warning.

Unity’s serialization system only supports enum types that are 32 bits or smaller (byte, sbyte, short, ushort, int, and uint). Enums with underlying types of long (64-bit signed) or ulong (64-bit unsigned) exceed this limit and aren’t serialized correctly, which causes data loss after a serialization cycle (for example, when saving a scene or reloading the domain).

Incorrect example: Using a 64-bit enum for serialization:

// INCORRECT: Using a 64-bit enum for serialization
public enum LargeEnum : long
{
    Value1 = 1L,
    Value2 = 2L,
    Value3 = long.MaxValue
}

public class GameData : MonoBehaviour
{
    public LargeEnum largeEnum; // UAC1011: 64-bit enum exceeds Unity's 32-bit limit
}

Correct example: Use a 32-bit-or-smaller enum underlying type:

// CORRECT: Use a 32-bit-or-smaller enum underlying type
public enum SmallEnum : int
{
    Value1 = 1,
    Value2 = 2,
    Value3 = int.MaxValue
}

public class GameData : MonoBehaviour
{
    public SmallEnum smallEnum; // OK - 32-bit enum is supported
}

Correct example: Mark the enum field as non-serialized:

If you don’t need the enum field to persist (for example, it’s runtime-only state), either make it non-public or mark it with [System.NonSerialized] if it must remain a public field:

// CORRECT: Mark the enum field as non-serialized
public enum LargeEnum : long
{
    Value1 = 1L,
    Value2 = 2L
}

public class GameData : MonoBehaviour
{
    [System.NonSerialized] public LargeEnum largeEnum; // OK - explicitly marked as non-serialized
    
    private void Awake()
    {
        // Initialize runtime enum value
        largeEnum = LargeEnum.Value1;
    }
}

Suppressing warnings

If you have a valid reason to suppress a specific analyzer warning, you can use the #pragma warning disable directive:

#pragma warning disable UAC1001
public class IntentionallyUnserializedClass
{
    public int value;
}
#pragma warning restore UAC1001

Suppressing these warnings is not recommended, because they indicate important serialization issues that can cause problems at runtime.

Limitation: base class field analysis

The analyzer only analyzes fields on types that are serialization roots (types marked with [Serializable], types inheriting from UnityEngine.Object, etc.). The analyzer doesn’t analyze the fields of base classes that aren’t serialization roots, even if they are part of an inheritance hierarchy with a serialization root. In the following example, DerivedClass triggers the UAC1002 warning because its base class BaseClass lacks the [Serializable] attribute, but the fields on BaseClass itself (settings and data) don’t trigger any warnings because BaseClass isn’t a serialization root.

public class BaseClass
{
    public Dictionary<string, int> settings; // No UAC1009 — BaseClass isn't a serialization root
    public CustomData data; // No UAC1001 — BaseClass isn't a serialization root
}

public class CustomData { }

[System.Serializable]
public class DerivedClass : BaseClass // UAC1002: BaseClass lacks [Serializable]
{
    public int value; // This field is analyzed
}

To get warnings for the base class fields, mark the base class with the [Serializable] attribute so it becomes a serialization root:

[System.Serializable]
public class BaseClass
{
    public Dictionary<string, int> settings; // UAC1009: reported
    public CustomData data; // UAC1001: reported
}

public class CustomData { }

[System.Serializable]
public class DerivedClass : BaseClass
{
    public int value;
}

Mark base classes with the [Serializable] attribute if you need their fields to be validated. This makes them serialization roots and enables the analyzer to check their fields.

Avoiding common serialization issues

Unity skips fields that don’t follow its serialization rules, often without producing a visible error, which can cause unexpected data loss. The analyzer detects these issues at compile time. To avoid the most common problems, follow these practices:

  • Add [Serializable] to every custom class you intend to serialize.

    This applies to both inline serialization and [SerializeReference].

  • Mark every class in an inheritance hierarchy with [Serializable].

    The attribute isn’t inherited, so each class must be explicitly marked.

  • Use [SerializeReference] for complex relationships.

    If your data structure involves null references, polymorphism, shared references, or cycles, use [SerializeReference] instead of inline serialization.

  • Avoid cycles in inline serialization.

    Without [SerializeReference], direct self-reference cycles cause Unity to ignore the field entirely, and indirect cycles are serialized with a 10-level recursion limit. Use indices, IDs, or other indirect references to avoid cycles.

  • Don’t use [SerializeReference] on value types.

    Structs, primitives, and enums are always serialized inline. [SerializeReference] only works with reference types (classes).

Additional Resources

Serialization rules
Custom serialization