Skip to main content

๐Ÿ“‹ Schemas

Schemas define the structure, default values, and migrations for your data. This guide covers everything you need to know about schemas in Coppermind.


Creating a Schemaโ€‹

local PlayerSchema = Coppermind.registerSchema({
name = "PlayerData",
dataTemplate = {
coins = 0,
gems = 0,
inventory = {},
},
migrations = {},
})
Unique Names

Each schema must have a unique name โ€” this is used as the DataStore name.


๐Ÿ“ Data Templatesโ€‹

The dataTemplate defines the default structure for new data:

local dataTemplate = {
-- Primitives
coins = 0,
playerName = "",
isPremium = false,

-- Nested tables
stats = {
level = 1,
xp = 0,
playTime = 0,
},

-- Arrays
inventory = {},
completedQuests = {},

-- Complex nested structures
settings = {
audio = {
musicVolume = 1,
sfxVolume = 1,
},
display = {
showDamageNumbers = true,
},
},
}

๐Ÿ”„ Data Reconciliationโ€‹

When loading existing data, Coppermind automatically reconciles it with the template:

ScenarioBehavior
Missing fieldsAdded with default values
Extra fieldsPreserved (not removed)
Nested structuresRecursively reconciled
-- If saved data is:
{ coins = 500 }

-- And template is:
{ coins = 0, gems = 0, stats = { level = 1 } }

-- โœ… Loaded data becomes:
{ coins = 500, gems = 0, stats = { level = 1 } }

๐Ÿ” Retrieving Schemasโ€‹

Get a registered schema by name:

local schema = Coppermind.getSchema("PlayerData")

if schema then
print("Found schema:", schema.name)
end

๐Ÿ”’ Type Safetyโ€‹

For better type inference, define your schema with type annotations:

type PlayerData = {
coins: number,
gems: number,
inventory: { string },
stats: {
level: number,
xp: number,
},
}

local PlayerSchema = Coppermind.registerSchema({
name = "PlayerData",
dataTemplate = {
coins = 0,
gems = 0,
inventory = {},
stats = {
level = 1,
xp = 0,
},
} :: PlayerData,
migrations = {},
})
Type Benefits

Type annotations provide:

  • Better autocomplete in your IDE
  • Compile-time error checking
  • Self-documenting code

โœ… Best Practicesโ€‹

1. Use Descriptive Namesโ€‹

-- โœ… Good
local PlayerSchema = Coppermind.registerSchema({
name = "PlayerData_v1",
-- ...
})

-- โŒ Avoid
local schema = Coppermind.registerSchema({
name = "Data",
-- ...
})

2. Initialize All Fieldsโ€‹

No Nil Values

Always provide default values for every field โ€” never use nil.

-- โœ… Good
dataTemplate = {
coins = 0,
lastLogin = 0,
settings = {
musicEnabled = true,
},
}

-- โŒ Avoid leaving fields undefined
dataTemplate = {
coins = nil, -- Don't do this
}

3. Keep Templates Flat When Possibleโ€‹

Deeply nested structures are harder to work with:

-- โœ… Prefer flat structures
dataTemplate = {
audioMusicVolume = 1,
audioSfxVolume = 1,
displayShowDamage = true,
}

-- โš ๏ธ Use nesting sparingly
dataTemplate = {
settings = {
audio = {
volumes = {
music = 1, -- Very deep!
},
},
},
}

4. Version Your Schema Namesโ€‹

If you need to make breaking changes, version your schema:

-- Original
local PlayerSchemaV1 = Coppermind.registerSchema({
name = "PlayerData_v1",
-- ...
})

-- New version with breaking changes
local PlayerSchemaV2 = Coppermind.registerSchema({
name = "PlayerData_v2",
-- ...
})
Breaking Changes

Changing the schema name creates a new DataStore. Use migrations for non-breaking changes instead.