Migration Guide from v4 to v5

v5 is a near-complete rewrite of the Lua bindings. The primary goal is to stay as close as possible to the C++ Steamworks SDK. This guide explains what changed since v4, and is useful to port your code, as it will require some manual work. It is recommended to read the Getting Started section as well.

The old API was hand-written and made many convenience choices (renaming fields, auto-dealing with buffer sizes). The new API auto-generates bindings directly from the API and mirrors the C++ signatures as closely as Lua allows. This may be less ergonomic to use, however, it scales better, we can support many more functions much more quickly. Implementing each function in v4 took a lot of work to analyze precisely what it does and how to make it as ergonomic as possible in Lua.

BENEFITS:

More functions supported: The first release of v5 supported over 800 hundred Steam API functions (over 90% of all Steam API functions), while v4 supported only 122.

Less mental overhead: Now the API works almost exactly the same as the Steam API, so you can just read that documentation and follow it, instead of having to constantly look up two distinct documentations.

DRAWBACKS:

Less ergonomic: Sometimes you have to deal with buffer sizes, bitmasks, and other C++-isms manually.


Naming: Everything Is PascalCase

Every interface name and every function name is PascalCase, matching the C++ class and method names exactly.

Interface subtables

Old

New

Steam.apps

Steam.Apps

Steam.extra

Steam.Extra

Steam.friends

Steam.Friends

Steam.user

Steam.User

Steam.userStats

Steam.UserStats

Steam.utils

Steam.Utils

All other interfaces (Steam.UGC, Steam.NetworkingSockets, etc.) follow the same rule.

Top-level lifecycle functions

Old

New

Steam.init()

Steam.Init()

Steam.shutdown()

Steam.Shutdown()

Steam.runCallbacks()

Steam.RunCallbacks()

Fields in returned tables (structs in C++) now also use their exact C++ names, instead of the simplified ones from v4. For example, entry.m_nGlobalRank rather than entry.globalRank for LeaderboardEntry_t.

GameServer lifecycle (moved out of the interface)

Steam.GameServer.Init / Shutdown / RunCallbacks were moved to the top level to mirror Steam.Init / Shutdown / RunCallbacks:

Old

New

Steam.GameServer.Init(...)

Steam.GameServerInit(...)

Steam.GameServer.Shutdown()

Steam.GameServerShutdown()

Steam.GameServer.RunCallbacks()

Steam.GameServerRunCallbacks()


Parameter, Function and Return Value Changes

Many functions now take a different number of parameters, or parameters of different types. Luasteam v4 aimed for ergonomics, so many useless parameters were removed (such as passing a table and also its size), while in v5 all parameters are required, so the conversion can be done automatically. The function return values might also be different, double check when migrating.

For example, UGC.getSubscribedItems() returned a table with the ids in v4, while UGC.GetSubscribedItems(cMaxEntries, bIncludeLocallyDisabled) in v5 takes a cMaxEntries integer with the size of the array to be allocated, which can be obtained from UGC.GetNumSubscribedItems(bIncludeLocallyDisabled), as well as taking an extra parameter bIncludeLocallyDisabled which took a default value in v4. Furthermore, it returns an integer as its first returned value (the size of the returned table), which was omitted in v4.

---- In v5 ----
local sz = Steam.UGC.GetNumSubscribedItems(false)
local _, items = Steam.UGC.GetSubscribedItems(sz, false)
registerItems(items)

---- In v4 ----
local items = Steam.UGC.getSubscribedItems()
registerItems(items)

In other times, some functions were fully omitted in v4, and used automatically under the hood, while in v5 they must be manually used. As an example, userStats.downloadLeaderboardEntries automatically called GetDownloadedLeaderboardEntry for you in v4, while in v5 you must call it manually.

---- In v5 ----
Steam.UserStats.DownloadLeaderboardEntries(handle, Steam.k_ELeaderboardDataRequestGlobal, 1, 1000, function(data, err)
    if err then
        print('Error happened')
    else
        for i = 0, data.m_cEntryCount - 1 do
            local ok, entry = Steam.UserStats.GetDownloadedLeaderboardEntry(data.m_hSteamLeaderboardEntries, i, 0)
            if ok then
                local name = Steam.Friends.GetFriendPersonaName(entry.m_steamIDUser)
                print('Rank #' .. entry.m_nGlobalRank .. ': ' .. name .. ' - ' .. entry.m_nScore)
            end
        end
    end
end)

---- In v4 ----
Steam.userStats.downloadLeaderboardEntries(handle, 'Global', 1, 1000, function(data, err)
    if err then
        print('Error happened')
    else
        for _, user in ipairs(data) do
            local name = Steam.friends.getFriendPersoneName(user.steamIDUser)
            print('Rank #' .. user.globalRank .. ': ' .. name .. ' - ' .. user.score)
        end
    end
end

Enums and Constants

All C++ enum constants are exposed as plain integers on the Steam table, using their exact C++ names. Thus, they can be accessed by using Steam.<CppConstantName>.

In v4, the enum values were not exposed anywhere, and functions that received enums often used strings with the enum name, now, they receive integers directly.

Old

New

Value

'Installed'

Steam.k_EItemStateInstalled

4

'Descending'

Steam.k_ELeaderboardSortMethodDescending

2

'Reliable'

Steam.k_ESteamNetworkingSend_Reliable

8

When a function returns or a callback field contains an enum value, it is always a plain Lua integer. You can compare it directly to the constant:

if data.m_eResult ~= Steam.k_EResultOK then
    print("Error:", data.m_eResult)
end

Bit Flags

Some API values are bitmasks — an integer where each bit represents an independent boolean flag. GetItemState, GetPersonaStateFlags, and others work this way, either to receive bitmask parameter or return bitmask values.

In v4, these values were returned as tables with many booleans, while in v5 they follow the C++ format exactly and return integers. In LuaJIT, use the built-in bit library for bitwise operations:

---- In v5 ----
local state = Steam.UGC.GetItemState(itemId)
-- state is an integer

local installed = bit.band(state, Steam.k_EItemStateInstalled) ~= 0
local needsUpdate = bit.band(state, Steam.k_EItemStateNeedsUpdate) ~= 0

---- In v4 ----
local state = Steam.UGC.getItemState(itemId)
-- state is a table with boolean fields subscribed, legacyItem, ...

local installed = state.installed
local needsUpdate = state.needsUpdate

Structs

Many Steamworks functions take or return C++ structs. luasteam v5 exposes these as Lua userdata with field access via metamethods, while v4 used different methods, tables or strings depending on which API method. See Structs on how to build and use them.

---- In v5 ----
local addr = Steam.newSteamNetworkingIPAddr {}
addr:ParseString("127.0.0.1:55556")
local opt1 = Steam.newSteamNetworkingConfigValue_t()
opt1:SetInt32(Steam.k_ESteamNetworkingConfig_TimeoutInitial, 1000)
local id = Steam.NetworkingSockets.ConnectByIPAddress(addr, 1, {opt1})

---- In v4 ----
local id = Steam.networkingSockets.connectByIPAddress("127.0.0.1:55556", {TimeoutInitial = 1000})
-- Note many types of options were not supported in v4 while all are in v5

Callbacks and CallResults

Callbacks and CallResults work very similar to v4, except for the names (and possibly types) of the fields in the callback structs, which now fully match the C++ name (e.g. m_eResult instead of result).


GameServer Variants

The initialization and shutdown of gameserver moved to be next to the core functions, so change Steam.gameServer.init(...) in v4 to Steam.GameServerInit(...) in v5. Also, now the interfaces that have a gameserver variant use acessors with the same name as in the C++ Steam API, so you can use Steam.GameServerUtils.XXX for the gameserver variant of Steam.Utils.XXX, for example.


Quick-Reference: Breaking Changes from v4

Area

Old

New

Lifecycle

Steam.init()

Steam.Init()

Lifecycle

Steam.runCallbacks()

Steam.RunCallbacks()

Lifecycle

Steam.shutdown()

Steam.Shutdown()

Lifecycle

Steam.gameServer.init()

Steam.GameServerInit()

Interfaces

Steam.userStats

Steam.UserStats (all PascalCase)

Methods

requestCurrentStats()

RequestCurrentStats() (all PascalCase)

Methods

apps.isDlcInstalled

Apps.BIsDlcInstalled (exact C++ name)

Extra

Steam.extra.parseUint64

Steam.Extra.ParseUint64

Callback fields

data.result, data.success, …

data.m_eResult, data.m_bSuccess, … (exact C++ struct field names)

Leaderboard entries

downloadLeaderboardEntries returned ready-made array

Returns LeaderboardScoresDownloaded_t; call GetDownloadedLeaderboardEntry(handle, i, 0) per entry (0-based index)

DownloadLeaderboardEntries “Friends”

no range args

must pass 0, 0 for range (parameters not omitted)

GetSubscribedItems

getSubscribedItems()

GetSubscribedItems(GetNumSubscribedItems(false), false)

GetItemState

returned table with .installed etc.

returns integer bitmask; check with bit.band(state, Steam.k_EItemStateInstalled) ~= 0

GetItemUpdateProgress

returned status as string

returns integer EItemUpdateStatus; compare to Steam.k_EItemUpdateStatus*

GetItemInstallInfo

getItemInstallInfo(id)

GetItemInstallInfo(id, bufferSize)

Enums

Used as strings

flat on Steam table as Steam.k_* integers