May 11, 2026

2026 Lua Scripting Example for a Highrise Leaderboard System

lua scripting example for a highrise leaderboard system

TL;DR

A Highrise Lua leaderboard system is a server-side code pattern in Highrise Studio that records player scores, validates scoring actions, and retrieves ranked entries using the Leaderboard API. The server should always be the score authority, not the client. This article walks through the definition, a working Lua scripting example for a Highrise leaderboard system, the difference between Leaderboard and Storage APIs, and the common mistakes that trip up new creators.

What Does “Lua Scripting Example for a Highrise Leaderboard System” Mean?

Break the phrase into three parts:

Lua scripting means writing Lua code to control game logic and interactivity inside Highrise Studio. Highrise uses Lua (not C#) as the supported scripting language for building interactive experiences, covering everything from object behavior to player input handling and game states.

Highrise leaderboard system refers to score and ranking logic built for a Highrise World. The Leaderboard API is designed specifically for player rankings, high-score tracking, and competitive game statistics across multiple boards (daily, weekly, all-time).

Example is the practical part. Creators searching this phrase want a small, copyable code pattern showing the flow: a player earns points, the server validates the action, the leaderboard updates, and rankings become available.

Put together, a Lua scripting example for a Highrise leaderboard system is a compact server-side Lua pattern that increments or sets player scores, stores them under a leaderboard ID, and retrieves ranked entries so players can compare performance.

Key Terms at a Glance

Term Meaning
Server script Code running on the server. Should handle all trusted score changes.
Client script Code running on the player’s device. Should not be trusted for final score decisions.
Leaderboard ID A string naming a leaderboard, like daily_scores or global_scores.
LeaderboardEntry A record containing id, name, score, and rank.
Callback A function passed into an API call that runs when the result is available.
Rate limit A cap on how often API calls can be made before throttling kicks in.
NetworkValue A synchronized value type for live UI updates across server and clients.

How a Highrise Leaderboard System Works

The mental model is a pipeline with clear boundaries between what the client does and what the server controls.

Player performs a scoring action (collects coin, finishes race)
   ↓
Client detects or signals the action to the server
   ↓
Server validates the action (Does this coin exist? Already claimed?)
   ↓
Server calls IncrementScoreForPlayer or SetScoreForPlayer
   ↓
Server fetches top entries with GetEntries
   ↓
UI displays player names, scores, and ranks

This flow relies on client-server communication through events like FireServer and FireClient. The client requests, the server decides, and the leaderboard API records the result.

The most important rule is straightforward: the server is the score authority. The client can display scores or request actions, but the server validates everything before calling leaderboard methods. Highrise’s security best practices warn explicitly that client events can be intercepted and manipulated, making server-side validation essential.

If you’re new to building interactive Highrise Worlds, this architecture might feel like extra work. It’s not optional. Skipping server validation is the fastest path to a broken leaderboard.

Minimal Server-Side Lua Example

Here is a Lua scripting example for a Highrise leaderboard system that awards points after a server-approved action. This is educational, not a production drop-in script, but it shows the correct pattern.

Adding Points to a Player’s Score

--!Type(Server)

local LEADERBOARD_ID = "global_scores"

local function awardPoints(player: Player, points: number)
    if points <= 0 then
        return
    end

    Leaderboard.IncrementScoreForPlayer(
        LEADERBOARD_ID,
        player,
        points,
        function(entry, error)
            if error ~= LeaderboardError.None then
                print("Leaderboard error: " .. tostring(error))
                return
            end

            print(entry.name .. " score: " .. entry.score)
            print("Rank: #" .. entry.rank)
        end
    )
end

server.PlayerConnected:Connect(function(player: Player)
    print(player.name .. " joined the world.")
end)

-- Call this only after the server validates the scoring action.
function AwardCoinPoints(player: Player)
    awardPoints(player, 10)
end

Line-by-Line Breakdown

--!Type(Server) marks the script as server-side. Highrise script type attributes include Client, Server, Module, ClientAndServer, and UI.

LEADERBOARD_ID is the string that names this leaderboard. You can create multiple boards (like daily_scores and weekly_scores) just by using different strings.

IncrementScoreForPlayer adds points to a player’s existing score and returns the updated entry with the new rank and score in a callback. This is a server-only method.

The callback checks LeaderboardError.None before proceeding. The error enum also includes values for invalid IDs, throttling (RequestThrottled), and timeouts.

Notice that the client never sends a score amount. The server decides the point value (10 in this case). This follows Highrise’s security guidance directly.

Retrieving the Top 10 Leaderboard Entries

A leaderboard is only useful if players can see it. Here’s how to fetch ranked entries:

--!Type(Server)

local LEADERBOARD_ID = "global_scores"

local function loadTopTen()
    Leaderboard.GetEntries(LEADERBOARD_ID, 1, 10, function(entries, error)
        if error ~= LeaderboardError.None then
            print("Could not fetch leaderboard: " .. tostring(error))
            return
        end

        for _, entry in ipairs(entries) do
            print(entry.rank .. ". " .. entry.name .. ": " .. entry.score)
        end
    end)
end

GetEntries takes a leaderboard ID, a starting rank, and a limit. Starting at rank 1 with a limit of 10 gives you the standard top-10 list. Each entry includes rank, name, score, and id.

One thing to watch: do not call this every frame. Leaderboard API calls are rate limited, and excessive requests return RequestThrottled. Call it on meaningful events, like when a player opens the leaderboard UI or when a match ends.

When to Use Leaderboard vs. Storage vs. NetworkValue

This is where many creators get confused. The Highrise platform offers several tools that look similar but serve different purposes.

What You Need What to Use Why
Live score during a match Lua table or NetworkValue Fast session state. NetworkValue can sync to clients for UI updates.
Official ranked leaderboard Leaderboard API Built for ranked entries and top-N retrieval.
Persistent custom player data Storage API Persists across sessions and world restarts.
Custom high-score with special rules Storage with UpdateValue Prevents lost updates when the new value depends on the old one.
Display only UXML + USS + Lua binding Keeps presentation separate from score logic.

The practical rule: if you need ranks, use the Leaderboard API. If you need custom persistent data that goes beyond simple scores, use Storage. If you need live UI updates during gameplay, use NetworkValue with its Changed event on the client.

For UI display, Highrise supports World UI (useful for in-world billboards or leaderboard displays), Above Chat, and HUD output modes. UXML defines the structure, USS handles styling, and Lua adds functionality through bindings.

The Safe Scoring Pattern (and the Unsafe One)

This section is the difference between a leaderboard that works and one that gets exploited within hours.

The Bad Pattern

-- BAD: client sends arbitrary score amount
AddScoreEvent:FireServer(999999)

The server receives a number from the client and blindly increments. Any player with basic tools can fire this event with any value they want. Practitioners on Reddit consistently flag this as the most common leaderboard vulnerability across game platforms, not just Highrise. One developer thread about online leaderboards highlighted that client-calculated scores are trivially easy to fake, making server validation a universal concern.

The Good Pattern

-- GOOD: client signals an action, not a score
CoinCollectedEvent:FireServer(coinId)

The server then checks whether that coin exists, whether it was already collected, whether the player is in a valid state, and only then awards a fixed number of points.

--!Type(Server)

local COIN_POINTS = 10
local collectedCoinsByPlayer = {}

local function canCollectCoin(player: Player, coinId: string)
    collectedCoinsByPlayer[player.user.id] = collectedCoinsByPlayer[player.user.id] or {}

    if collectedCoinsByPlayer[player.user.id][coinId] then
        return false
    end

    -- Server-side checks:
    -- Does this coin exist in the world?
    -- Is the player close enough to collect it?
    -- Is the game currently active?

    collectedCoinsByPlayer[player.user.id][coinId] = true
    return true
end

function AwardCoinIfValid(player: Player, coinId: string)
    if not canCollectCoin(player, coinId) then
        return
    end

    Leaderboard.IncrementScoreForPlayer("global_scores", player, COIN_POINTS, function(entry, error)
        if error ~= LeaderboardError.None then
            print("Leaderboard update failed: " .. tostring(error))
            return
        end

        print(player.name .. " earned " .. COIN_POINTS .. " points.")
    end)
end

Notice the use of player.user.id rather than player.id. The Storage docs clarify that player.user.id is a permanent unique identifier, while player.id is a temporary session ID. Using the wrong one means your tracking table resets every session.

Why Fairness Matters in Highrise

Highrise Reddit discussions surface recurring complaints about perceived unfairness in competitive systems, including concerns about automation, alt accounts, and macro use. These are anecdotal community reports, not verified platform facts, but they point to something real: players care deeply about leaderboard integrity. A server-authoritative scoring pattern is the minimum standard.

If you’re building competitive experiences for the Highrise community, investing time in validation logic pays off in player trust.

Common Mistakes

Mistake 1: Calling Leaderboard Functions from the Client

The Leaderboard API is server-side only. There is no workaround. If your script has --!Type(Client) at the top, leaderboard methods will not work.

Mistake 2: Updating Scores Too Often

Every leaderboard call counts against a rate limit. Spamming IncrementScoreForPlayer on every frame or every tiny action will trigger RequestThrottled errors. Better approach: accumulate points in a local variable during active gameplay, then commit the score at checkpoints or match end.

Mistake 3: Deleting Entries on Disconnect for Persistent Boards

The official testing example deletes a player’s leaderboard entry when they disconnect. That makes sense for temporary test sessions. It does not make sense for all-time or weekly high-score boards. Treat disconnect deletion as a testing pattern, not a production default.

Mistake 4: Using Reset Casually

Reset clears an entire leaderboard. It exists for testing and seasonal resets. Calling it accidentally in production destroys all player scores with no undo.

Mistake 5: Get-Then-Set Race Conditions

If you use Storage for custom high-score logic, the Storage docs warn that a get-then-set pattern can lose data when two players update the same value simultaneously. Use UpdateValue instead, which handles the prior-value dependency safely.

Mistake 6: Confusing player.id with player.user.id

player.id is a temporary session identifier. player.user.id is the permanent unique ID. Using player.id as a storage key or tracking identifier means your data disappears when the session ends.

Try This Next

Lua community discussions, especially from creators learning similar platforms, consistently advise pairing documentation with small projects rather than watching tutorials passively. Practitioners on Reddit recommend actually building something small to internalize the concepts.

Here’s a progression for building your own Lua scripting example for a Highrise leaderboard system:

  1. Create a server script and print a message when a player joins.
  2. Add a test leaderboard ID and award fixed points on join.
  3. Print the updated score and rank from the callback.
  4. Fetch the top 10 entries and print them.
  5. Add a UI element to display the leaderboard in-world.
  6. Replace the “award on join” trigger with a validated gameplay action.
  7. Add rate-limit-friendly timing so you batch updates instead of spamming calls.

If you want to explore what other creators have built, browse Highrise Worlds to see leaderboard systems in action. For creator inspiration, check out Highrise Ideas or read through the latest updates on the Highrise blog.

Ready to start building? Download Highrise and set up your first World with Highrise Studio.

FAQ

Can a Highrise leaderboard script run on the client?

Score writes cannot run on the client. The Leaderboard API is server-side only, and Highrise security docs warn that client events can be manipulated. The client should only display results or signal actions for the server to validate.

Should I use SetScoreForPlayer or IncrementScoreForPlayer?

Use IncrementScoreForPlayer when a player accumulates points over time, like collecting coins or completing tasks. Use SetScoreForPlayer when the score is an absolute result, like a final race time converted into points. Both are server-side Leaderboard API methods.

How do I show the top 10 players?

Call GetEntries with a starting rank of 1 and a limit of 10. Loop through the returned entries and display each one’s rank, name, and score in your UI. The entries come back sorted by rank automatically.

Do I need Storage for a leaderboard?

Not necessarily. Use the Leaderboard API when you need ranked scoreboards with built-in ranking. Use Storage when you need custom persistent data, complex progress tracking, or high-score logic that doesn’t fit a standard ranked board.

How do I prevent leaderboard cheating?

Keep score authority on the server. Let the client signal actions (like “I touched the coin”), but validate those actions server-side before awarding points. Never let the client specify the score amount. This follows Highrise’s official security guidance.

Should I delete leaderboard entries when players disconnect?

Only for temporary session-based boards. The official testing example uses disconnect deletion, but that pattern is wrong for all-time, weekly, or any persistent high-score leaderboard.

What happens if I call the Leaderboard API too often?

You get a RequestThrottled error. The LeaderboardError enum defines this as a rate-limit response. Batch your updates and avoid calling leaderboard methods every frame or on rapid-fire events.

What is the difference between player.id and player.user.id?

player.user.id is the permanent unique identifier for a player across all sessions. player.id is a temporary session ID that changes every time the player joins. Always use player.user.id for storage keys, tracking tables, and anything that needs to persist.

© 2026 Pocket Worlds. All rights reserved.