-
Notifications
You must be signed in to change notification settings - Fork 0
Versioning Strategy
A well-designed versioning strategy is essential for maintaining saved data compatibility as an addon evolves.
LibSavedVars uses version numbers and migrations together to safely transform existing user data into the current format. A good versioning strategy ensures that users can upgrade from any previous addon release without losing their settings.
The saved variable version should represent the structure of the saved data, not the addon release number.
For example:
Addon Version: 2.5.0
Saved Data Version: 4
The addon may have many releases that do not change saved data.
Increase the saved variable version only when the stored data format changes.
Increase the version when existing saved data requires transformation.
Examples:
Before:
{
showNames = true
}After:
{
displayNames = true
}Requires migration:
settings:Migrate(2, function(savedVars)
savedVars.displayNames = savedVars.showNames
savedVars.showNames = nil
end)Before:
{
scale = 1.2
}After:
{
ui = {
scale = 1.2
}
}Requires a migration because the location changed.
Before:
{
color = "red"
}After:
{
color = {
r = 1,
g = 0,
b = 0,
}
}The old value must be converted.
Before:
Account-wide
└── build = "Tank"
After:
Character
└── build = "Tank"
Requires migration because the storage model changed.
Many addon changes do not require a new saved data version.
Example:
Version 1:
local defaults = {
enabled = true,
}Version 2:
local defaults = {
enabled = true,
showTooltips = true,
}No migration is needed.
LibSavedVars automatically supplies the default value:
settings.showTooltips
-- trueA new feature that does not modify existing saved data does not require a version change.
Examples:
- New commands.
- New UI panels.
- New calculations.
- New event handlers.
Use simple incremental numbers:
1
2
3
4
5The saved data version is not intended to represent:
- Major addon releases.
- Minor releases.
- Patch numbers.
Avoid:
1.2.3or:
250based on addon versions.
A data version answers:
"What format is this saved data?"
Each version should have a migration that converts the previous format into the new format.
Example:
settings:Migrate(2, function(savedVars)
-- Convert version 1 data to version 2
end)
settings:Migrate(3, function(savedVars)
-- Convert version 2 data to version 3
end)
settings:Migrate(4, function(savedVars)
-- Convert version 3 data to version 4
end)Upgrade path:
Version 1
|
v
Migration 2
|
v
Migration 3
|
v
Migration 4
|
v
Current Version
Once a migration has been released, treat it as permanent.
Bad:
Version 2 migration
changed after release
Good:
Version 2 migration
unchanged forever
Version 3 migration
added later
Users may upgrade directly from very old versions.
Removing or changing old migrations can break their saved data.
Each migration should perform one logical change.
Good:
settings:Migrate(3, function(savedVars)
savedVars.newName = savedVars.oldName
savedVars.oldName = nil
end)Avoid:
settings:Migrate(3, function(savedVars)
-- rename settings
-- move tables
-- convert formats
-- remove old data
-- rebuild profiles
end)Large migrations are harder to test and debug.
Migrations execute in ascending version order.
Example:
settings:Migrate(2, migration2)
settings:Migrate(5, migration5)
settings:Migrate(4, migration4)Execution order:
Migration 2
Migration 4
Migration 5
The order migrations are registered does not determine execution order.
When creating a new saved variable structure:
Prefer:
{
ui = {
scale = 1.0,
theme = "Dark",
},
combat = {
mode = "Normal",
},
}over:
{
uiScale = 1.0,
uiTheme = "Dark",
combatMode = "Normal",
}Grouped structures are easier to extend.
Do not create deeply nested structures unless they provide value.
Hard to maintain:
settings.options.display.interface.window.position.xEasier:
settings.window.xDesign tables that can expand naturally.
Example:
profiles = {
Default = {},
Tank = {},
Healer = {},
}is easier to extend than separate unrelated tables.
Every migration should be tested with old data.
Recommended tests:
No existing data:
Version 0 → Current Version
Expected:
- Defaults load correctly.
- No migration errors.
Example:
Version 3 → Version 4
Expected:
- Only migration 4 runs.
Example:
Version 1 → Version 5
Expected:
Migration 2
Migration 3
Migration 4
Migration 5
Users rarely have default values only.
Test:
{
scale = 2.0,
theme = "Custom",
}Ensure customizations survive migration.
Migrations should fail safely.
Avoid:
savedVars.newValue =
savedVars.oldTable.valueif oldTable may not exist.
Prefer:
if savedVars.oldTable then
savedVars.newValue =
savedVars.oldTable.value
endUsers may have incomplete or unusual saved data due to:
- Previous bugs.
- Manual editing.
- Interrupted updates.
- Removed addons.
When changing saved data:
- Identify whether existing data changes.
- Increase the saved data version if needed.
- Add a migration for the change.
- Keep the migration permanent.
- Test upgrades from older versions.
- Release the update.
A good LibSavedVars versioning strategy:
- Uses versions to track data formats.
- Separates addon versions from data versions.
- Increases versions only when data changes.
- Uses migrations for transformations.
- Keeps migrations permanent.
- Tests upgrades from old data.
- Designs saved structures for future growth.
A carefully planned version strategy allows an addon to evolve for years while preserving user configuration.