SerializeField in Unity: Show Private Fields in the Inspector

Illustration of an inspector panel with sliders beside a cube

Beginner Unity tutorials make every field public so it shows up in the Inspector. It works, but it also lets any script in the project change that value, which is how “something keeps resetting my speed” bugs are born. [SerializeField] gives you Inspector editing without exposing the field to other code. This guide explains how Unity serialization works, why [SerializeField] private is the professional default, and the Inspector attributes that turn messy components into clean, designer-friendly ones.

Quick answer

[SerializeField] makes Unity save a private field and show it in the Inspector, without letting other scripts change it: [SerializeField] private float moveSpeed = 5f;. Prefer it to public fields. If a field still doesn’t show, it’s probably static, readonly, a property, or a type Unity can’t serialize such as a Dictionary.

Watch it in action: a public field reset by another script vs a [SerializeField] private one, plus the inspector attributes in the real editor (3:03).


What Serialization Means in Unity

Serialization is how Unity saves the values you set in the Inspector into scenes, prefabs and ScriptableObject assets, then restores them when loading. A field shows in the Inspector because it’s serialized. If Unity doesn’t serialize a field, its value isn’t saved, and it resets to the code default every time the scene loads.

A field is serialized if it is public or marked [SerializeField], is not static, const or readonly, and has a type Unity can serialize.

Unity Inspector showing the private Speed and Attack Range fields of an Enemy component after adding SerializeField.
With [SerializeField], the private Speed and Attack Range fields appear in the Inspector and stay editable.

Public vs [SerializeField] Private

public class Enemy : MonoBehaviour
{
    public float speed = 3f;                 // visible in Inspector AND writable by any script

    [SerializeField] float attackRange = 2f; // visible in Inspector, private to this class

    public float AttackRange => attackRange;  // read-only access for other scripts
}

Both appear in the Inspector, but speed can be changed by enemy.speed = 0 anywhere in the project. With attackRange, other scripts can read it through the property but can’t change it. When a value is wrong, you only have one class to check.

The convention most studios follow: fields are private, and anything that needs editing in the Inspector gets [SerializeField]. Expose data to other scripts through properties or methods. It’s the same principle as in our C# naming and style guide.

The reverse also exists: [System.NonSerialized] public makes a public field that Unity doesn’t save or show, and [HideInInspector] hides a serialized field while still saving it.

Side by side Unity scene: with a public speed field a trap script sets every enemy to speed 0, while with a SerializeField private speed the enemies keep speed 3 and walk on.
Left: speed is public, so Trap.cs sets it to 0 and every enemy freezes. Right: speed is [SerializeField] private, so the trap can't touch it.

What Unity Can and Can’t Serialize

Serializable Not serializable (by default)
int, float, bool, string, enums, double, long Dictionary, HashSet, Queue, Stack
Vector2/3/4, Quaternion, Color, Rect, Bounds, AnimationCurve, Gradient, LayerMask Properties (get/set)
References to UnityEngine.Object (GameObject, components, assets, ScriptableObjects) Static, const and readonly fields
Arrays and List<T> of serializable types Nested lists (List<List<T>>) and multidimensional arrays
Your classes and structs marked [System.Serializable] Interfaces and abstract types (without [SerializeReference])

Need a dictionary in the Inspector? Serialize a list of key/value structs and build the Dictionary in Awake, or use one of the serializable-dictionary tools in our Asset Store tools roundup.

Inspector Attributes You’ll Use Every Day

public class Weapon : MonoBehaviour
{
    [Header("Damage")]
    [Tooltip("Damage dealt per hit before armor.")]
    [SerializeField, Min(0)] float damage = 10f;
    [SerializeField, Range(0f, 1f)] float critChance = 0.1f;

    [Header("Firing")]
    [SerializeField] float fireRate = 4f;
    [Space(8)]
    [SerializeField] LayerMask hitMask;

    [Header("Flavour")]
    [SerializeField, TextArea(2, 4)] string description;
    [SerializeField, ColorUsage(false, true)] Color muzzleGlow = Color.yellow;   // HDR colour

    [SerializeField, HideInInspector] int internalVersion;
}
Attribute What it does
[Header("...")] Bold section heading above the field
[Tooltip("...")] Hover text explaining the field
[Range(min, max)] Slider, clamped in the Inspector
[Min(x)] Prevents values below x
[Space(px)] Vertical gap
[TextArea] / [Multiline] Multi-line text box
[ColorUsage] / [GradientUsage] Alpha and HDR options
[HideInInspector] Saved but hidden
[FormerlySerializedAs("oldName")] Keeps values after renaming a field
[ContextMenu("Name")] (on a method) Adds a method to the component’s ⋮ menu
[RequireComponent(typeof(T))] (on a class) Auto-adds a needed component

Ranges and Min only clamp values entered in the Inspector, not values set from code. Validate in code too if it matters. For more advanced Inspector customization, see our Editor scripting 101 guide.

Unity Inspector of a Weapon component organised with Header sections, a Crit Chance Range slider, a TextArea and an HDR color field.
The same Weapon with [Header], [Range], [Space], [TextArea] and an HDR color: grouped, clamped and easy to read.

Serializing Your Own Classes and Structs

Group related settings into a class or struct and mark it [System.Serializable]. It shows as a foldout:

[System.Serializable]
public class Loot
{
    public ItemType item;
    [Min(1)] public int amount = 1;
    [Range(0f, 1f)] public float dropChance = 0.5f;
}

public class LootTable : MonoBehaviour
{
    [SerializeField] Loot[] drops;
}

Plain serializable classes are stored by value: two fields can’t share the same instance, and null becomes a default instance after serialization. For shared data, use a ScriptableObject reference instead (see our ScriptableObjects guide).

Unity Inspector of a Loot Table component with a Drops array of a Serializable Loot class, each element showing Item, Amount and a Drop Chance slider.
A [System.Serializable] Loot class in an array: each element gets its own Item, Amount and Drop Chance fields.

Properties, Renaming and SerializeReference

Auto-properties aren’t serialized, but you can target their hidden backing field:

[field: SerializeField] public int MaxHealth { get; private set; } = 100;

This shows “Max Health” in the Inspector and gives a public getter with a private setter in one line.

Renaming a field loses every value already set in scenes and prefabs, because Unity saves values by field name. Add [FormerlySerializedAs("oldName")] (from UnityEngine.Serialization) before renaming, open and save the affected assets, then remove it later.

[SerializeReference] serializes fields by reference, which allows interfaces, abstract base classes, polymorphic lists and shared or null instances:

public interface IEffect { void Apply(GameObject target); }
[System.Serializable] public class DamageEffect : IEffect { public float amount; public void Apply(GameObject t) { } }
[System.Serializable] public class SlowEffect : IEffect { public float duration; public void Apply(GameObject t) { } }

public class Ability : MonoBehaviour
{
    [SerializeReference] List<IEffect> effects = new();
}

Unity’s default Inspector doesn’t offer a “choose type” dropdown for these. You can add elements from code, use a small custom drawer, or use an Inspector extension package.

OnValidate and Reset

OnValidate runs in the Editor whenever a value changes in the Inspector, which makes it great for enforcing rules or updating previews:

void OnValidate()
{
    if (maxAmmo < clipSize) maxAmmo = clipSize;
    if (!rb) rb = GetComponent<Rigidbody>();
}

void Reset()   // runs when the component is first added, or via Reset in the ⋮ menu
{
    hitMask = LayerMask.GetMask("Default", "Enemy");
}

Keep OnValidate light, and don’t create or destroy objects in it, since it runs often, including during asset imports.

🔧 Want a much better Inspector?

Inspector extensions add buttons, conditional fields, validation and dictionary support with simple attributes. We cover the ones worth buying in our tools roundup.

See our favourite Unity tools →

Troubleshooting

My field doesn’t show in the Inspector

It’s private without [SerializeField], it’s a property, static or readonly, or its type isn’t serializable (Dictionary, or a custom class missing [System.Serializable]). A compile error also stops the Inspector updating, so check the Console.

Changing the default value in code does nothing

Existing components keep their serialized Inspector value, and the code default only applies to newly added components. Change it in the Inspector, or use the component’s Reset.

Values reset after I renamed a field

Unity matches saved data by name. Rename back, add [FormerlySerializedAs], then rename again.

Values I set in Play mode were lost

Play mode changes to scene objects are discarded when you stop. Copy the component (⋮ > Copy Component) before stopping, then Paste Component Values after. Note that changes to ScriptableObject assets in Play mode do persist.

A List of my class shows as empty foldouts

The class’s fields are private without [SerializeField], or the class lacks [System.Serializable].

FAQ

What does [SerializeField] do in Unity?

It tells Unity to save a private field’s value and show it in the Inspector, without making the field accessible to other scripts.

Should I use public fields or SerializeField?

Use [SerializeField] private fields for Inspector values, and expose them through properties only when other scripts need them.

Can Unity serialize a Dictionary?

Not natively. Serialize a list of key/value pairs and build the dictionary at runtime, or use a serializable dictionary package.

How do I show a property in the Inspector?

Use an auto-property with [field: SerializeField], or a private serialized backing field with a public getter.

What is SerializeReference?

An attribute that serializes a field by reference, which supports interfaces, abstract classes, polymorphic lists and null values.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *