Skip to content

Godot quick start ​

Add leaderboards to a game you've already built — copy one folder, wire a few calls. Works on web, desktop, and mobile.

Starting from scratch, or just trying it out?

The full template is a working Godot 4 project with an example game, menus, and a leaderboard already wired up. Open it, run the Setup Wizard, and you're submitting scores in ~3 minutes — see the Godot 4 guide. This page is the path for adding CheddaBoards to a game you already have.

Before you start ​

  • Godot 4.3+. This guide uses await (Godot 4 syntax). On Godot 3.6, replace await CheddaBoards.wait_until_ready() with yield(CheddaBoards, "sdk_ready") — see the Godot 3.6 guide.
  • A game that already produces a score and has a game-over moment to submit from.
  • A CheddaBoards game — register one at cheddaboards.com and copy your Game ID (my-game) and API key (cb_my-game_xxxxxxxxx).

Step 1 — Add the addon ​

Recommended — the Godot Asset Store package. It's the addon-only build and ships the editor plugin files: drop addons/cheddaboards/ into your project, enable CheddaBoards under Project → Project Settings → Plugins, and the autoload registers itself. Done.

Alternatively — from GitHub. The addon repo is the SDK's canonical home: copy its addons/cheddaboards/ folder into your project and enable the plugin exactly as above. (The template repo vendors the same addon, but if you copy the folder out of the template instead, register the autoload yourself.) To register it by hand, run the wizard (File → Run → addons/cheddaboards/SetupWizard.gd) or add it under Project → Project Settings → Autoload:

Name: CheddaBoards
Path: res://addons/cheddaboards/CheddaBoards.gd

Step 2 — Wire it up ​

Everything below goes in one script — wherever your game starts (e.g. MainMenu.gd): credentials, login, submitting a score, and reading the leaderboard.

gdscript
extends Control  # or whatever your start scene is

func _ready():
    # Credentials must come before any other CheddaBoards call.
    CheddaBoards.set_api_key("cb_my-game_xxxxxxxxx")
    CheddaBoards.set_game_id("my-game")

    # Connect the leaderboard signal ONCE here — not inside a function,
    # or you'll reconnect it every time you open the board.
    CheddaBoards.leaderboard_loaded.connect(_on_leaderboard)

    # Wait for the SDK, then log in.
    # submit_score fails until login has completed.
    await CheddaBoards.wait_until_ready()
    CheddaBoards.login_anonymous()   # no name — see note below


# Call from YOUR game-over code, with the final score and streak.
func _on_game_over(score: int, streak: int):
    CheddaBoards.submit_score(score, streak)


# Call when you want to show the board (e.g. a button press).
func show_leaderboard():
    CheddaBoards.get_leaderboard("score", 100)


# Fires when get_leaderboard() returns. Connected once, in _ready().
func _on_leaderboard(entries: Array):
    for e in entries:
        print("#%d %s - %d" % [e.rank, e.nickname, e.score])

Log in without a name: returning players keep the nickname they already saved, and brand-new players get a server-assigned name (Player_1248) when their first submit creates the profile. get_nickname() returns "" until a profile fetch or rename has told the SDK the name — fetch the profile after the first submit if you want to display or highlight it. Only pass a name to login_anonymous() when the player has just chosen it, because a passed name becomes the current nickname and is written to the server on the next submit — overwriting whatever they had. To let players pick or change their name, use change_nickname() (see Nicknames); a complete name-entry scene is on Player names.

That's the whole integration: call _on_game_over(score, streak) when a run ends, and show_leaderboard() from a button. You're on the board. For anti-cheat, add Step 3.

A play session tells the backend a real run just started, so it can validate the score against elapsed time and reject anything impossible. Three calls:

  • Start when a run actually begins — at the start of gameplay, not in _ready().
  • Submit as normal. The SDK attaches the active session token for you; you don't pass it manually.
  • Clear once submission finishes — on both success and failure.
gdscript
func _ready():
    # …credentials + login from Step 2…
    CheddaBoards.score_submitted.connect(_on_score_submitted)
    CheddaBoards.score_error.connect(_on_score_error)
    CheddaBoards.play_session_error.connect(_on_session_error)

# The moment the player starts a run.
func start_run():
    if CheddaBoards.is_ready():
        CheddaBoards.start_play_session()
    # …your own game-start code…

# Run ends — submit. The session token is attached automatically.
func _on_game_over(score: int, streak: int):
    CheddaBoards.submit_score(score, streak)

func _on_score_submitted(score: int, streak: int):
    CheddaBoards.clear_play_session()

func _on_score_error(reason: String):
    CheddaBoards.clear_play_session()

# Non-fatal: the score still submits, it just won't be time-validated.
func _on_session_error(reason: String):
    push_warning("Play session error: %s" % reason)

Set the actual limits (max score per submission, streak caps) from your dashboard's Security tab — see Anti-cheat. Skip the session entirely and scores still submit — unless you've enabled time validation for the game, in which case the session token is required and sessionless submits are rejected.

Drop-in means you start the session

The SDK attaches the token for you, but it does not start or end sessions on its own — on this path, the three calls above are yours to make. (The template's game wrapper is what runs the lifecycle automatically.)

Done ​

Anonymous login, score submission, global leaderboards, and anti-cheat play sessions — on web, desktop, and mobile, in about ten minutes.

Quick reference ​

Sign-in ​

gdscript
# Anonymous — works everywhere, no account needed.
# Log in nameless: returning players keep their saved nickname (see Step 2).
CheddaBoards.login_anonymous()

# Google / Apple on any platform, via device code
CheddaBoards.login_with_device_code()

CheddaBoards.device_code_received.connect(func(user_code, verification_url, qr_data_url):
    print("Go to %s and enter: %s" % [verification_url, user_code])
    # qr_data_url is a base64 PNG — decode into a TextureRect for scanning.
)
CheddaBoards.device_code_approved.connect(func(nickname):
    print("Welcome, %s!" % nickname)
)

if CheddaBoards.is_authenticated():
    print("Logged in as ", CheddaBoards.get_nickname())

Device code sign-in is a one-time flow — the session is saved to user:// and restored on startup, so returning players are already signed in. If the server rejects a stored session, the SDK clears it and emits session_expired + logout_success. Full flow: Authentication.

Scores & leaderboards ​

gdscript
CheddaBoards.submit_score(1000, 5)          # score, streak
CheddaBoards.get_leaderboard("score", 100)  # "score" or "streak"
CheddaBoards.get_scoreboard("weekly", 50)
CheddaBoards.get_player_rank()

Nicknames ​

gdscript
CheddaBoards.change_nickname("NewName")

CheddaBoards.nickname_changed.connect(func(new_nickname):
    print("Now playing as ", new_nickname)
)
CheddaBoards.nickname_error.connect(func(reason):
    print("Nickname change failed: ", reason)
)

Nicknames are 3–16 characters, letters, digits, and underscores. A name that's already taken isn't an error — it's auto-suffixed (Chedz → Chedz_1) and nickname_changed reports the name actually applied. Only genuinely invalid names raise nickname_error, and that's permanent for that value — ask for a different one rather than retrying. Before a brand-new player's first score, change_nickname() holds the name locally and sends it with that submit; redraw from get_nickname() on profile_loaded to pick up what the server stored. Full model and a drop-in name-entry scene: Player names.

Achievements (optional) ​

You decide when an achievement is earned; the SDK stores it. The safest pattern is to send a run's unlocks with the score:

gdscript
func _on_game_over(score: int, streak: int):
    var earned := []
    if score >= 1000:
        earned.append("score_1000")
    if streak >= 5:
        earned.append("streak_5")
    # Submits the score first, then syncs the achievements once it's saved.
    CheddaBoards.submit_score_with_achievements(score, streak, earned)

# Fires once per achievement the server confirms.
CheddaBoards.achievement_unlocked.connect(func(id): print("Unlocked: ", id))

For a brand-new anonymous player, the score submit is what creates them on the backend — which is why achievements ride along with it rather than going first. Once the player exists, CheddaBoards.unlock_achievement("id") (or unlock_achievements_batch([...])) unlocks mid-run too. Re-sending an achievement the player already has is harmless. Upgrading to a signed-in account later merges them. See Achievements.

Want auto-unlocks, popups and offline caching?

That's the Achievements autoload, which ships with the template rather than the addon. You can copy autoloads/Achievements.gd out of the template into your project — then replace its definitions and its check_* conditions with your own game's.

Common issues ​

IssueFix
"API key not set" / "Game ID not set"Call set_api_key(...) and set_game_id(...) in _ready() before any other call (the SDK ships with empty defaults)
"Not authenticated"Submit ran before login. await wait_until_ready() then login_anonymous() before any submit_score()
await won't parseYou're on Godot 3.6 — use yield(CheddaBoards, "sdk_ready"). See the Godot 3.6 guide
4-arg profile_loaded errorsplay_count is now the 5th arg — add a trailing play_count: int
Score rejected as too fast / impossibleStart a play session before the run so the backend can time-validate it
Leaderboard fires twice / duplicatesYou connected leaderboard_loaded inside a function — connect it once in _ready()
Leaderboard emptyVerify game_id matches the one in your dashboard
"CheddaBoards not found"Enable the plugin (Asset Store install), add it to Autoloads, or run the Setup Wizard
Blank screen (web)Serve it — python3 -m http.server, then the localhost URL, not file://
"Engine not defined" (web)The web export must be named index.html, not MyGame.html

Full list: Errors.

See also: Godot 4 guide (full template walkthrough) · Signals reference · Anti-cheat · REST API

Can't be taken away. Open source backend and SDKs.