Library / SDK
sinbad/SPUD avatar
sinbad/SPUD

SPUD saves Unreal Engine actor state without a hand written save system

Steve's Persistent Unreal Data library

613 stars95 forksC++MIT

At a glance

What is it?
SPUD is an Unreal Engine 5 plugin that persists chosen UObject state to disk, including across streamed level loads, by marking classes with a marker interface and flagging properties with SaveGame. The saving is easy to opt into, and the sharp edges are the ordering requirement around unsaved levels and the naming rules for objects the gameplay framework spawns for you.
Who is it for?
SPUD fits a UE5 team that has levels streaming in and out and needs object state to survive the transition, or that wants a save format driven by property flags rather than a bespoke serialisation class per actor.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two features, and the second one is the interesting one

SPUD is described as a save game and streaming level persistence solution for Unreal Engine 5, and it names two core features: saving and loading game state easily, and streamed levels retaining their state as they unload and reload without a save needed.

That second feature is the distinguishing one. In a UE5 project with World Partition or level streaming enabled, an actor in a streamed level is destroyed when the stream unloads and recreated when it reloads. Any state it was carrying is gone, and the standard answer is a save system that serialises it and restores it on load. SPUD is built around doing that automatically for actors you opt in.

The rest of the feature list is what makes the opt-in cheap. You mark actors in your levels as persistent by implementing ISpudObject, a marker interface with no required methods. You pick which properties to save by enabling the SaveGame option. All property types are supported, including arrays, references between objects and custom structs. You can also manually mark non level UObjects, for example a GameInstance, for inclusion in the save. Dynamically spawned objects that exist at save time are re-spawned on load, and level objects that have been destroyed are automatically re-destroyed on level load. Core details such as transform, controller rotation and physics state are saved automatically. And the whole thing is usable in C++ or Blueprints.

The primary language is C++, the project is MIT licensed, and the repository is not archived with the last push on 2026-10-15, though it publishes no GitHub releases.

The interface is a marker, which is the whole opt-in

The mechanism for opting an actor in is deliberately the smallest possible thing. You implement ISpudObject on your class, and in C++ that means adding it as a second base class:

c++
class AMyActor : public AActor, public ISpudObject
{
...

The documentation is explicit that you do not have to implement any methods on this interface. It is solely a marker so that SPUD will know to look at your object. Any actor marked this way will be automatically made persistent, and that includes GameModes and GameStates.

For a Blueprint workflow there is no code to write, only a checkbox in the class settings, and the README points to a screenshot showing the interface being implemented in Blueprints.

The design decision here is worth naming, because it is the difference between a library you adopt and a library you fight. A marker interface means zero boilerplate per class and no per class serialisation code, so adding persistence to a hundred actor types is a checkbox each. The cost is that the interface cannot carry any per class configuration, so if you want different actors to be saved in different ways, that configuration has to live somewhere else in the project.

It also means the library decides what to write. Everything an actor holds that is not explicitly opted out is part of its persisted state, and the developer is choosing what to include, not constructing a record.

Properties are selected with the SaveGame flag

Once a class is marked persistent, the standard state is saved automatically, and the standard list is specific: the hidden flag, the transform for movable objects, controller rotation for pawns, physics velocities for physics objects, and the velocity of any movement component that is present, for example player movement or projectile movement.

Beyond that, you choose which additional properties to save with the SaveGame UPROPERTY flag:

c++
	UPROPERTY(SaveGame)
	int MySavedInt;

In Blueprints the same thing is done in the advanced property details section, and the README links a screenshot of that panel.

The documentation notes that for the most common case of an object in a level, that is all there is to it, and that many types of property are supported, with a properties document referenced for more detail.

The SaveGame flag is not a SPUD invention. It is an Unreal Engine mechanism, and the practical consequence is that SPUD does not need its own annotation system or its own per type serialisation code. If a property is flagged SaveGame, the engine already knows how to serialise it, which is why arrays, references between objects and custom structs all work without special cases. A team already using SaveGame for its own save system will find the flag familiar; a team introducing it will find it is the standard way to mark a property for serialisation in Unreal.

Unsaved levels are the one thing you must get right

There is a block in the documentation marked VERY IMPORTANT, and it is the first thing to read if you are evaluating this plugin.

You must save all your levels before playing in editor. Failure to do so results in mis-categorisation of some level objects. The Output Log will dump an error about this when unsaved levels are detected.

There are two fixes given. Use the menu option File -> Save All Levels before playing in the editor, or in Project Settings > Plugins > SPUD, enable the option Save All Levels On Play In Editor. The second is described as the best bet for making sure you do not accidentally have strange bugs, at the expense of a slight delay to PIE if you have unsaved levels. The closing line is blunt: do not report any bugs unless you have checked your levels are saved.

The reason this matters is structural. SPUD distinguishes between objects that were part of the level and objects that were spawned at runtime, and it makes that distinction at save time by consulting the level data in the editor session. An unsaved level means the level in memory and the level on disk disagree, and the categorisation can be wrong.

This is a genuine sharp edge rather than a documentation nit. The symptom, mis-categorised level objects, would look like a persistence bug in the plugin, and the fix is in the editor workflow. Enabling the project setting is the cheap insurance, at the cost of a slightly slower play in editor session when levels are dirty.

Runtime and framework spawned objects need different identifiers

SPUD handles three categories of object, and each has a different identification rule, which is where most of the integration work goes.

For actors that are not part of the level but are spawned at runtime, and that also implement ISpudObject, they are automatically re-spawned on load. Because these objects need to be uniquely identified, the class must have a special FGuid property called SpudGuid:

c++
	UPROPERTY()
	FGuid SpudGuid;

You do not have to assign a value, SPUD will generate a GUID if it is blank. The documentation adds that you should not mark it as SaveGame, because it is not your save state, just metadata SPUD needs to uniquely identify the object.

For objects spawned automatically by the gameplay framework during level initialisation, there is a different problem. These include pawns and characters, PlayerState, GameState and GameMode. They are neither stored in the level nor explicitly spawned by your game code, so SPUD cannot generate a GUID for them at runtime and save it. If you have state in these objects you need to implement the OverrideName method in ISpudObject, in C++ or Blueprints, to give these instances a unique, pre-defined name.

Global objects are the third case. Objects like GameInstance are not picked up for persistence even if they implement ISpudObject, because they are not included in the world. You opt them in explicitly:

c++
	GetSpudSubsystem(GetWorld())->AddPersistentGlobalObjectWithName(this, "MyGameInstance");	

Global objects must always exist, SPUD will not re-create them on load, but it will re-populate their state.

Destroyed actors are re-destroyed, which is the subtle part

There is a behaviour in SPUD that is easy to read past and easy to be surprised by.

If a level actor that implements ISpudObject is destroyed, that destruction is made persistent by SPUD. Re-loading a map will automatically re-create that actor, but as part of the restore process SPUD will destroy it again, returning the world to the correct state. You do not need to do anything extra to make this work.

Read that again in the context of a save and load cycle. An actor that the player blew up, or that a script removed, is recorded as destroyed. When the map reloads, the engine recreates the actor because the level file still contains it, and SPUD then destroys it to match the saved state. The end result is the same world you had when you saved.

This is a nice property, because it means the destruction is part of the persisted state rather than something your game logic has to re-apply. It also means the load path performs a destroy on an actor that has just been created, which is exactly the kind of behaviour that looks like a bug if you have not read about it, and exactly the kind of behaviour that can interact badly with your own destruction handlers. An actor that reacts to being destroyed by spawning something, cleaning up a reference or notifying a subsystem will be running that code during a load.

The project also documents a Properties document for the details of what is supported, and the repository points to a separate SPUD Examples project for working code.

Where SPUD is the wrong tool

Four situations rule it out.

The first is a game where every actor comes from the gameplay framework. If your world is composed of pawns, PlayerState, GameState and GameMode spawned at level initialisation, and you place nothing in the level by hand, then the streaming persistence problem does not exist and the whole apparatus is overhead. Worse, it is overhead with a cost, because those framework objects are exactly the ones that need the OverrideName method implemented to be handled at all.

The second is a multiplayer game. The documentation describes a local persistence solution, a save file on disk that is written and read back. It says nothing about server authority, replication of the saved state, or how a client and a server agree on what was persisted. A networked game where the server owns the world state needs the persistence to happen on the authority and the result to be replicated, which is a different design.

The third is a project that already has a mature save system. If your actors already implement a custom serialisation interface and you have load code that handles version migrations, SPUD is a replacement for work you have done, and adopting it means throwing that away or running two systems side by side.

The fourth is a project where save format versioning is not a concern, which sounds like a benefit but is really a risk. A persistent data format outlives the session that wrote it, and the documentation does not describe how SPUD handles a save file written by an older build of your game. Any game with a shipped version and a player who comes back after an update has to answer that question, and with SPUD you will be answering it for a format you do not control.

The alternative worth naming is Unreal's own SaveGame system with a UGameplayStatics save slot, which is built into the engine, needs no plugin, and has the versioning story handled through a version field in the save class. Its weakness is exactly the one SPUD addresses, which is that you write the serialisation code yourself for each actor and there is no automatic handling of streamed levels. The choice is between writing the code yourself with full control over the format, and taking the automatic behaviour with a format you did not design.

Editorial conclusion

SPUD fits a UE5 team that has levels streaming in and out and needs object state to survive the transition, or that wants a save format driven by property flags rather than a bespoke serialisation class per actor. It is the wrong choice for a project whose actors are all spawned by the gameplay framework and never placed in a level, since the overhead of the persistent data format buys nothing there, and it is the wrong choice for a multiplayer authoritative game, since the documentation describes a local persistence solution with no mention of server authority. Before you adopt it, check three things: that your actors can be identified at load time, since runtime spawned actors need a SpudGuid property and framework spawned objects need an OverrideName method, that you will actually save your levels, because the documentation is emphatic that unsaved levels cause mis-categorisation of level objects, and that your save format versioning is handled, since a persistent data format outlives the session that wrote it.

Frequently asked questions

What is SPUD and what does it do?

SPUD is a save game and streaming level persistence solution for Unreal Engine 5. It has two core features: saving and loading game state easily, and streamed levels retaining their state as they unload and reload without a save being needed.

How do I make an actor persistent in SPUD?

Implement the ISpudObject interface on the class, either in C++ by adding it as a second base class or in Blueprints through the class settings. The interface has no required methods, it is solely a marker so SPUD knows to look at the object, and any actor marked this way is automatically made persistent.

How do I choose which properties SPUD saves?

By enabling the SaveGame option on the property, which is the UPROPERTY(SaveGame) flag in C++ or the advanced property details section in Blueprints. Standard state such as the hidden flag, transform, controller rotation and physics velocities is saved automatically without any flag.

Why does SPUD mis-categorise level objects in the editor?

The documentation states that you must save all your levels before playing in editor, because failing to do so results in mis-categorisation of some level objects, and the Output Log reports an error when unsaved levels are detected. The fix is File -> Save All Levels, or enabling Save All Levels On Play In Editor in Project Settings > Plugins > SPUD.

How are runtime spawned actors identified for saving?

The class must have a special FGuid property called SpudGuid, which SPUD will generate if it is blank. It must not be marked as SaveGame, since it is metadata SPUD needs to identify the object rather than part of the save state. Objects spawned by the gameplay framework instead need the OverrideName method implemented.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. sinbad/SPUD on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sinbad-spud.svg)](https://hysenlabs.com/projects/sinbad-spud)

Was this useful?