Skip to main content

Submit Score

The submitscore method posts a player's score to a leaderboard. Scores are automatically ranked based on the leaderboard's configuration, and players can optionally attach metadata to their submissions.

Overview

Submit scores when players complete levels, achieve milestones, or finish matches. The Moitribe backend automatically handles ranking, timespan categorization, and deduplication based on your leaderboard settings.

warning

Players must be authenticated to submit scores. Check authentication status before attempting to submit.

Basic Usage

JavaScript Example

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'high-scores',
score: 1500,
callback: (result) => {
if (result.success) {
console.log('Score submitted successfully!');
} else {
console.error('Failed to submit score:', result.msg);
}
}
});

TypeScript Example

import MoitribeSDK from '@veniso/moitribe-js';

interface SubmitScoreParams {
leaderboardid: string;
score: number;
scoretag?: string;
callback: (result: { success: boolean; msg?: string }) => void;
}

const params: SubmitScoreParams = {
leaderboardid: 'high-scores',
score: 1500,
callback: (result) => {
if (result.success) {
console.log('Score submitted successfully!');
} else {
console.error('Failed:', result.msg);
}
}
};

MoitribeSDK('my-game-id', 'submitscore', params);

Parameters

ParameterTypeRequiredDescription
leaderboardidstringYesThe leaderboard ID to submit to
scorenumberYesThe score value to submit
scoretagstringNoOptional metadata (JSON string)
callbackfunctionYesCalled with the result

Leaderboard ID

The leaderboardid must match an ID from your game's leaderboard metadata:

// Get valid IDs from metadata
MoitribeSDK('my-game-id', 'loadleaderboardmetadata', {
onlyData: true,
callback: (result) => {
if (result.success) {
result.leaderboards.forEach((lb) => {
console.log('Valid ID:', lb.leaderboardID);
});
}
}
});

Score Value

The score parameter must be a number:

  • For high-score leaderboards: Higher values rank better
  • For time-trial leaderboards: Lower values rank better
  • Use integers for whole numbers: 1500, 10000
  • Use floats for decimals: 99.99, 123.45
tip

Convert time values to consistent units before submitting. For example, convert minutes to milliseconds or seconds for consistency.

Score Tag (Optional)

The scoretag parameter allows you to attach metadata to a score:

const metadata = {
level: 5,
difficulty: 'hard',
playTime: 120,
powerupsUsed: 3
};

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'high-scores',
score: 1500,
scoretag: JSON.stringify(metadata),
callback: (result) => {
console.log('Score with metadata submitted');
}
});

Response Format

The callback receives a result object:

{
success: boolean;
msg?: string; // Error message if success is false
}

Success Response

{
success: true
}

Error Response

{
success: false,
msg: 'Player not authenticated'
}

Common Use Cases

Submit After Level Completion

Post score when player finishes a level:

function onLevelComplete(level, score) {
// Check if player is authenticated
MoitribeSDK('my-game-id', 'isAuthenticated', {}, (authResult) => {
if (authResult.success) {
// Submit score with level info
MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'high-scores',
score: score,
scoretag: JSON.stringify({ level: level }),
callback: (result) => {
if (result.success) {
showSuccessMessage('Score submitted to leaderboard!');
} else {
console.error('Score submission failed:', result.msg);
}
}
});
} else {
// Prompt to log in
showLoginPrompt('Log in to save your score to the leaderboard!');
}
});
}

// Usage
onLevelComplete(5, 1500);

Submit Multiple Leaderboards

Post the same score to different leaderboards:

function submitToMultipleLeaderboards(score, metadata) {
const leaderboards = ['high-scores', 'daily-challenge', 'weekly-contest'];

leaderboards.forEach((leaderboardId) => {
MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
scoretag: JSON.stringify(metadata),
callback: (result) => {
if (result.success) {
console.log(`Submitted to ${leaderboardId}`);
}
}
});
});
}

// Submit to all leaderboards
submitToMultipleLeaderboards(1500, { level: 5, difficulty: 'hard' });

Submit Time-Based Score

Convert time to appropriate format:

function submitTimeScore(minutes, seconds, milliseconds) {
// Convert to total milliseconds
const totalMs = (minutes * 60000) + (seconds * 1000) + milliseconds;

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'speed-run',
score: totalMs,
scoretag: JSON.stringify({
displayTime: `${minutes}:${seconds}.${milliseconds}`,
timestamp: Date.now()
}),
callback: (result) => {
if (result.success) {
console.log('Time submitted successfully!');
}
}
});
}

// Submit a time of 2:34.567
submitTimeScore(2, 34, 567);

Submit with Rich Metadata

Include detailed game context:

function submitScoreWithContext(score, gameData) {
const metadata = {
score: score,
level: gameData.level,
difficulty: gameData.difficulty,
playTime: gameData.playTime,
powerupsUsed: gameData.powerupsUsed,
deaths: gameData.deaths,
accuracy: gameData.accuracy,
timestamp: Date.now(),
version: '1.2.0'
};

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'high-scores',
score: score,
scoretag: JSON.stringify(metadata),
callback: (result) => {
if (result.success) {
console.log('Score with context submitted');
}
}
});
}

// Usage
submitScoreWithContext(1500, {
level: 5,
difficulty: 'hard',
playTime: 180,
powerupsUsed: 3,
deaths: 2,
accuracy: 0.85
});

Retry Failed Submissions

Implement retry logic for failed submissions:

function submitScoreWithRetry(leaderboardId, score, maxRetries = 3) {
let attempts = 0;

function attemptSubmit() {
attempts++;

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
callback: (result) => {
if (result.success) {
console.log('Score submitted successfully!');
} else {
if (attempts < maxRetries) {
console.log(`Retry ${attempts}/${maxRetries}...`);
setTimeout(attemptSubmit, 1000 * attempts); // Exponential backoff
} else {
console.error('Failed to submit score after', maxRetries, 'attempts');
showError('Unable to submit score. Please try again later.');
}
}
}
});
}

attemptSubmit();
}

// Usage
submitScoreWithRetry('high-scores', 1500, 3);

Queue Offline Submissions

Store scores when offline, submit when back online:

const scoreQueue = [];

function submitOrQueue(leaderboardId, score, scoretag) {
if (navigator.onLine) {
submitScore(leaderboardId, score, scoretag);
} else {
// Queue for later
scoreQueue.push({ leaderboardId, score, scoretag });
localStorage.setItem('scoreQueue', JSON.stringify(scoreQueue));
showMessage('Score saved. Will submit when online.');
}
}

function submitScore(leaderboardId, score, scoretag) {
MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
scoretag: scoretag,
callback: (result) => {
if (result.success) {
console.log('Score submitted');
}
}
});
}

// Process queue when back online
window.addEventListener('online', () => {
const queue = JSON.parse(localStorage.getItem('scoreQueue') || '[]');

queue.forEach((item) => {
submitScore(item.leaderboardId, item.score, item.scoretag);
});

localStorage.removeItem('scoreQueue');
});

TypeScript Submit Manager

Create a type-safe score submission manager:

interface ScoreMetadata {
level?: number;
difficulty?: string;
playTime?: number;
[key: string]: any;
}

class ScoreSubmitter {
private gameId: string;

constructor(gameId: string) {
this.gameId = gameId;
}

async submit(
leaderboardId: string,
score: number,
metadata?: ScoreMetadata
): Promise<boolean> {
return new Promise((resolve, reject) => {
MoitribeSDK(this.gameId, 'submitscore', {
leaderboardid: leaderboardId,
score: score,
scoretag: metadata ? JSON.stringify(metadata) : undefined,
callback: (result: { success: boolean; msg?: string }) => {
if (result.success) {
resolve(true);
} else {
reject(new Error(result.msg || 'Failed to submit score'));
}
}
});
});
}

async submitIfAuthenticated(
leaderboardId: string,
score: number,
metadata?: ScoreMetadata
): Promise<boolean> {
const isAuth = await this.checkAuthentication();

if (!isAuth) {
throw new Error('Player not authenticated');
}

return this.submit(leaderboardId, score, metadata);
}

private async checkAuthentication(): Promise<boolean> {
return new Promise((resolve) => {
MoitribeSDK(this.gameId, 'isAuthenticated', {}, (result: { success: boolean }) => {
resolve(result.success);
});
});
}
}

// Usage
const submitter = new ScoreSubmitter('my-game-id');

try {
await submitter.submitIfAuthenticated('high-scores', 1500, {
level: 5,
difficulty: 'hard'
});
console.log('Score submitted successfully!');
} catch (error) {
console.error('Submission failed:', error.message);
}

Score Validation

Client-Side Validation

Validate scores before submission:

function validateScore(score) {
if (typeof score !== 'number') {
return { valid: false, error: 'Score must be a number' };
}

if (!isFinite(score)) {
return { valid: false, error: 'Score must be finite' };
}

if (score < 0) {
return { valid: false, error: 'Score cannot be negative' };
}

if (score > Number.MAX_SAFE_INTEGER) {
return { valid: false, error: 'Score is too large' };
}

return { valid: true };
}

function submitValidatedScore(leaderboardId, score) {
const validation = validateScore(score);

if (!validation.valid) {
console.error('Invalid score:', validation.error);
showError(validation.error);
return;
}

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
callback: (result) => {
if (result.success) {
console.log('Valid score submitted');
}
}
});
}

Prevent Duplicate Submissions

Avoid submitting the same score multiple times:

let lastSubmittedScore = null;
let lastSubmitTime = 0;

function submitScoreOnce(leaderboardId, score) {
const now = Date.now();
const cooldown = 5000; // 5 seconds

if (score === lastSubmittedScore && (now - lastSubmitTime) < cooldown) {
console.log('Score already submitted recently');
return;
}

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
callback: (result) => {
if (result.success) {
lastSubmittedScore = score;
lastSubmitTime = now;
console.log('Score submitted');
}
}
});
}

Error Handling

Handle common submission errors:

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'high-scores',
score: 1500,
callback: (result) => {
if (result.success) {
showSuccessMessage('Score submitted to leaderboard!');
playSuccessSound();
} else {
console.error('Submission failed:', result.msg);

// Handle specific errors
if (result.msg.includes('authenticated') || result.msg.includes('login')) {
showLoginPrompt('Please log in to submit scores');
} else if (result.msg.includes('leaderboard')) {
showError('Leaderboard not found. Please contact support.');
} else if (result.msg.includes('network') || result.msg.includes('connection')) {
showError('Network error. Please check your connection.');
queueScoreForLater('high-scores', 1500);
} else {
showError('Failed to submit score. Please try again.');
}
}
}
});

Best Practices

1. Check Authentication First

Always verify authentication before submitting:

function submitScoreSafely(leaderboardId, score) {
MoitribeSDK('my-game-id', 'isAuthenticated', {}, (authResult) => {
if (authResult.success) {
// Proceed with submission
MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
callback: (result) => {
console.log('Submitted:', result.success);
}
});
} else {
showLoginPrompt('Log in to save your score!');
}
});
}

2. Provide User Feedback

Give clear feedback about submission status:

function submitWithFeedback(leaderboardId, score) {
// Show loading state
showLoading('Submitting score...');

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: score,
callback: (result) => {
hideLoading();

if (result.success) {
showSuccess('Score submitted successfully!');
setTimeout(() => {
showLeaderboard(leaderboardId);
}, 2000);
} else {
showError('Failed to submit score');
}
}
});
}

3. Include Relevant Metadata

Add context to help with analytics and debugging:

function submitWithMetadata(score, gameState) {
const metadata = {
level: gameState.level,
difficulty: gameState.difficulty,
timestamp: Date.now(),
gameVersion: '1.2.0',
platform: navigator.platform
};

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: 'high-scores',
score: score,
scoretag: JSON.stringify(metadata),
callback: (result) => {
console.log('Score with metadata submitted');
}
});
}

4. Handle Edge Cases

Account for special scenarios:

function submitScoreRobust(leaderboardId, score) {
// Validate score
if (!score || score < 0 || !isFinite(score)) {
console.error('Invalid score value:', score);
return;
}

// Round to reasonable precision
const roundedScore = Math.round(score * 100) / 100;

MoitribeSDK('my-game-id', 'submitscore', {
leaderboardid: leaderboardId,
score: roundedScore,
callback: (result) => {
if (result.success) {
console.log('Score submitted:', roundedScore);
}
}
});
}

Next Steps

Related topics: