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: