Appearance
Tutorial: Customize The Dashboard
This section explains how to add your own visualizations for the player's state to the dashboard.
Appearance
This section explains how to add your own visualizations for the player's state to the dashboard.
In the previous Tutorial: LiveOps Features chapter, we integrated game configs, segmentation, experiments, and LiveOps events to enable powerful over-the-air content updates and dynamic gameplay.
In this chapter, we will customize the LiveOps Dashboard to better visualize our game's specific data. The dashboard is built with Vue.js and uses Metaplay's Meta UI component library, making it straightforward to add custom visualizations that seamlessly integrate with the built-in UI.
By the end of this chapter, you'll have a custom dashboard project with game-specific components that display player currencies and inventory in a polished, professional way.
This guide takes you through the following steps:
To keep the tutorial focused, we will not dive deep into the SDK features themselves. Here are the concepts we'll be applying in this chapter, along with links for further reading if you're curious:
To add custom visualizations to the dashboard, we first need to create our own dashboard project. The Metaplay CLI will scaffold a full Vue.js project that's a customizable copy of the built-in dashboard, complete with all the components and tooling you need.
The new project will leverage our Meta UI component library, ensuring your custom views look and feel consistent with the built-in UI.
The dashboard project will be initialized in the Backend/Dashboard/ directory by the init dashboard command:
MyProject$ metaplay init dashboardThis step adds the the Backend/Dashboard/ directory which contains the game-specific dashboard files. The most relevant changes are:
metaplay-project.yaml to mark that a customized dashboard is in use.pnpm-workspace.yaml is used to link the game-specific dashboard to depend on the Metaplay SDK projects in MetaplaySDK/Frontend/.Backend/Dashboard/ contains the game-specific dashboard project and files. This now replaces the built-in dashboard in this project.Backend/Dashboard/src/gameSpecific.ts is the main integration point for customizing the dashboard.Start the server:
MyProject$ metaplay dev severWhile the server is running, start the customized LiveOps Dashboard with the CLI:
MyProject$ metaplay dev dashboardVisit the LiveOps Dashboard at http://localhost:5551.
Info
From now on, you should access the dashboard at port 5551 instead of the 5550 port we used previously. 5551 is the default port for the custom dashboard, while 5550 continues to serve the un-modified built-in dashboard.
With our custom dashboard project initialized, we can now add game-specific visualizations. We'll create Vue.js components using the Meta UI library and inject them into specific locations on the player details page — showing currencies in the overview section and inventory in a custom card.
Let's start by adding the player's currencies to the overview section at the top of the player details page. We'll use the addPlayerResources API to display both fishbones (soft currency) and premium currency:
initializationApi.addPlayerResources([
{
displayName: 'Fishbones',
getAmount: (playerModel): number => playerModel.playerData.numCoins,
},
{
displayName: 'Premium',
getAmount: (playerModel): number => playerModel.playerData.numPremium,
},
])Next, we'll create a custom card to visualize the player's inventory. Create a new ConsumablesCard.vue file for our custom card. The component uses Meta UI's meta-list-card and shows which consumables the player owns and how many of each they have:
<template lang="pug">
// Create a new card with a list of items
meta-list-card(
title="Consumables"
:item-list="allConsumables"
)
// For each item...
template(#item-card="{ item: consumable }")
MListItem(condensed)
// Render an avatar, name, and description in the bottom left section
template(#bottom-left)
div(class="tw-flex tw-gap-x-1")
img(
:src="consumable.avatarUrl"
class="tw-h-10 tw-w-10"
)
div
div(class="") {{ consumable.displayName }}
div(class="tw-italic tw-text-neutral-400") {{ consumable.description }}
// Render the amount of consumables in the bottom right section
template(#bottom-right)
div(v-if="consumable.amount") {{ consumable.amount }}
div(
v-else
class="tw-italic tw-text-neutral-400"
) None
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { getSinglePlayerSubscriptionOptions, isEpochTime } from '@metaplay/core'
import { MetaListCard } from '@metaplay/meta-ui'
import { MCard, MListItem } from '@metaplay/meta-ui-next'
import { useSubscription } from '@metaplay/subscriptions'
const props = defineProps<{
/**
* Id of the player whose consumable data we want to show.
*/
playerId: string
}>()
// Use Metaplay's built in data fetching utility to fetch the player's data.
const { data: playerData, refresh: playerRefresh } = useSubscription(() =>
getSinglePlayerSubscriptionOptions(props.playerId)
)
// Use Vue's Computed to automatically trigger an update when the player's data changes
const allConsumables = computed((): ConsumableDisplayData[] => {
return allConsumableDetails.map((consumableDetails) => {
return {
displayName: consumableDetails.displayName,
description: consumableDetails.description,
avatarUrl: consumableDetails.avatarUrl,
amount: playerData.value?.model.playerData.consumables[consumableDetails.id] || 0,
}
})
})
interface ConsumableDetails {
id: string
displayName: string
description: string
avatarUrl: string
}
const allConsumableDetails: ConsumableDetails[] = [
{
id: 'COIN_MAG',
displayName: 'Coin Magnet',
description: 'Attracts coins towards you for a limited time',
avatarUrl: '/Consumables/COIN_MAG.png',
},
{
id: 'SCORE_MULTIPLAYER',
displayName: 'Score Multiplier',
description: 'Doubles your score for a limited time',
avatarUrl: '/Consumables/SCORE_MULTIPLAYER.png',
},
{
id: 'INVINCIBILITY',
displayName: 'Invincibility',
description: 'Makes you invincible for a limited time',
avatarUrl: '/Consumables/INVINCIBILITY.png',
},
{
id: 'EXTRALIFE',
displayName: 'Extra Life',
description: 'Grants an extra life',
avatarUrl: '/Consumables/EXTRALIFE.png',
},
]
interface ConsumableDisplayData {
displayName: string
description: string
avatarUrl: string
amount: number
}
</script>Finally, we'll inject our new card into the player details page using the addUiComponent API:
initializationApi.addUiComponent(
'Players/Details/Tab0',
{
uniqueId: 'ConsumablesCard',
vueComponent: async () => await import('./ConsumablesCard.vue'),
width: 'half',
},
{ position: 'before', targetId: 'Inbox'}
)Start the customized LiveOps Dashboard with:
MyProject$ metaplay dev dashboardVisit the LiveOps Dashboard at http://localhost:5551 and check out the player details page to see the new components in action.

We have now created a custom dashboard project and added game-specific visualizations for player currencies and inventory. The dashboard is now easier to navigate for your specific game's data, and the patterns we've used here can be extended to visualize any aspect of your game.
While we've focused on simple component additions in this tutorial, larger projects often add entirely new pages to the sidebar, build custom analytics views, or create role-specific features for different team members.
That wraps up the hands-on implementation! In the next chapter, we'll review what we've built and explore where to go next with Metaplay.