Datapack and Assets
The Soccerverse datapack: JSON structure, customization and share flow, asset URL rules, cache behaviour and the default-pack fetch.
This page documents the Soccerverse datapack JSON and the frontend asset lookup path. It is a read-only/off-chain customization surface, not a game-state API: a datapack maps numeric game IDs to names, colours, and image metadata so clients can render player, club, league, cup, and stadium labels.
Purpose
Game and chain APIs expose IDs. The datapack turns those IDs into human-facing presentation data:
- player ID -> first name, surname, player image URL;
- club ID -> club name, primary RGB colour, club badge/shirt image URL;
- country/division -> league name and league image;
- country/cup ID -> cup name and cup image;
- stadium ID -> stadium name and optional stadium image.
The canonical frontend default is:
https://downloads.soccerverse.com/svpack/packv2/default.json
The older URL https://downloads.soccerverse.com/svpack/default.json is superseded; the packv2/default.json URL above is current.
Access notes
| Item | Value |
|---|---|
| Protocol | Plain HTTPS GET for a static JSON document and static asset URLs. |
| Auth | None observed for the default datapack URL. |
| Client override | Users can select the default datapack or enter a custom HTTPS URL in Settings. |
| Server override | NEXT_PUBLIC_DATAPACK_URL controls the default frontend URL; backend/updater code uses DATAPACK_URL in the updater environment. |
| Asset bases | The pack carries section-level baseImageUrl values; frontend deployment also provides NEXT_PUBLIC_CLUB_SHIRT_BASE_URL and image-base env vars. |
| Writes | None in this doc. Hosting a custom pack is done outside Soccerverse; selecting it in the frontend only changes local client settings. |
Top-level JSON structure
A datapack is a JSON object. Consumers accept either a top-level PackData object or, in some frontend code paths, a bare object shaped like PackData.
{
"PackData": {
"PlayerData": {
"baseImageUrl": "https://downloads.soccerverse.io/svpack/packv2/player_webp/",
"P": [{ "id": "1100", "f": "Erling", "s": "Braut Haaland" }]
},
"ClubData": {
"baseImageUrl": "#",
"C": [{ "id": "50", "n": "Manchester Blue", "rgb": "132,187,255" }]
},
"LeagueData": {
"baseImageUrl": "https://downloads.soccerverse.io/svpack/packv2/leagues/",
"L": [{ "c": "ENG", "d": "1", "n": "Premier League", "i": "ENG1.png" }]
},
"CupData": {
"baseImageUrl": "https://downloads.soccerverse.io/svpack/packv2/cups/",
"C": [{ "id": "ENG", "n": "FA Cup", "i": "ENG.png" }]
},
"StadiumData": {
"baseImageUrl": "#",
"S": [{ "id": "556", "n": "Old Trafford", "i": "556.jpg" }]
}
}
}
Field table
| Section | Row key | Main fields | Notes |
|---|---|---|---|
PlayerData | P | id, f, s | id is parsed as an integer. Frontend displays f + " " + s; missing data falls back to Player #<id>. |
ClubData | C | id, n, rgb | rgb is a decimal triplet string such as 132,187,255. If baseImageUrl is #, frontend builds shirt URLs from NEXT_PUBLIC_CLUB_SHIRT_BASE_URL. |
LeagueData | L | c, d, n, i | Datapack divisions are 1-based. Some API responses use 0-based divisions, so frontend lookup adds 1. |
CupData | C | id, n, i | Cup id is usually a country/region-style code; image URL is baseImageUrl + i where present. |
StadiumData | S | id, n, i | Frontend can return null image URL when no real stadium image is configured. |
Fetching the default pack
This fetch is read-only and does not require any secret.
python3 - <<'PY'
import json, urllib.request, hashlib
url='https://downloads.soccerverse.com/svpack/packv2/default.json'
req=urllib.request.Request(url, headers={'User-Agent':'Soccerverse-docs-readonly/1.0'})
with urllib.request.urlopen(req, timeout=30) as r:
status=r.status
content_type=r.headers.get('content-type')
data=r.read()
print('status', status)
print('content_type', content_type)
print('bytes', len(data))
print('sha256_12', hashlib.sha256(data).hexdigest()[:12])
obj=json.loads(data)
pack=obj.get('PackData', obj)
for section, arrkey in [('PlayerData','P'),('ClubData','C'),('LeagueData','L'),('CupData','C'),('StadiumData','S')]:
rows=pack.get(section,{}).get(arrkey,[])
print(section, len(rows), 'baseImageUrl=', pack.get(section,{}).get('baseImageUrl'))
def find(section, arrkey, key, val):
for row in pack.get(section,{}).get(arrkey,[]):
if str(row.get(key))==str(val): return row
print('player_1100', find('PlayerData','P','id',1100))
print('club_50', find('ClubData','C','id',50))
PY
The script prints per-section row counts and baseImageUrl values, then spot-checks a player and club row.
Frontend loading and lookup behaviour
The browser datapack manager is a singleton. It:
- reads the active URL from
useSettingsStore.getState().getCurrentDatapackUrl(); - fetches the JSON with
cache: 'force-cache'for normal initialization; - parses
PackDataand builds in-memory maps for players, clubs, leagues, cups, and stadiums; - exposes async lookup helpers such as
getPlayerInfo,getClubInfo,getLeagueInfo,getCupInfo, andgetStadiumInfo; - leaves
initialized=falseon fetch/parse failure so the next call can retry.
Important lookup conventions:
| Helper | Input | Output notes |
|---|---|---|
getPlayerInfo(playerId) | number | Returns datapack name/image or Player #<id> plus /images/player-placeholder.svg. |
getClubInfo(clubId) | number | Returns datapack club name/image/RGB or Club #<id> plus /images/club-placeholder.svg. |
getLeagueInfo(countryId, division) | country string plus API division | Adds 1 to the API division before lookup because datapack divisions are 1-based. |
getCupInfo(countryId, compType) | country/cup code and competition type | Handles special World Club Cup / Cup Winners Cup cases and otherwise uses CupData.C. |
getStadiumInfo(stadiumId) | number | Builds a stadium image URL only when StadiumData.baseImageUrl is configured and not #. |
getMatchClubImages(homeClubId, awayClubId, options) | club IDs plus size/kit options | Uses RGB similarity to choose away kit automatically when home and away colours are too close. |
Club/player/competition assets
Asset URL handling is split between datapack base URLs and frontend environment config.
| Asset | Source | URL construction |
|---|---|---|
| Player image | PlayerData.baseImageUrl | ${baseImageUrl}${playerId}.webp when the base contains webp, otherwise .png. |
| Club image / shirt | ClubData.baseImageUrl or NEXT_PUBLIC_CLUB_SHIRT_BASE_URL | If ClubData.baseImageUrl === "#", frontend uses ${NEXT_PUBLIC_CLUB_SHIRT_BASE_URL}${clubId}_${kit}_${size}.webp. |
| League image | LeagueData.baseImageUrl plus row i | ${baseImageUrl}${i}. |
| Cup image | CupData.baseImageUrl plus row i | ${baseImageUrl}${i}, with special fallbacks for some competition types. |
| Stadium image | StadiumData.baseImageUrl plus stadium ID | ${baseImageUrl}${stadiumId}.jpg when configured. |
Production frontend deployment source currently sets:
NEXT_PUBLIC_DATAPACK_URL=https://downloads.soccerverse.com/svpack/packv2/default.json
NEXT_PUBLIC_CLUB_SHIRT_BASE_URL=https://downloads.soccerverse.com/svpack/packv2/clubs/
NEXT_PUBLIC_GK_SHIRT_BASE_URL=https://downloads.soccerverse.com/shirts/webp/
The values above are public asset URLs from deployment config, not secrets.
Customization, import, and sharing
The frontend supports a user-selected datapack URL in Settings:
customDatapackUrlstores the active custom URL, ornullfor the default pack.datapackUrlHistorystores up to 3 recent custom URLs, de-duplicated with newest first.getCurrentDatapackUrl()returnscustomDatapackUrl || DEFAULT_DATAPACK_URL.- Adding a custom URL performs basic
new URL(...)validation before applying it. - Selecting a URL calls
datapackManager.refresh()and then reloads the page so rendered names/assets come from the newly selected pack.
For pack authors, "import" in the current frontend means "enter the hosted JSON URL"; it is not a file upload. To share a custom pack:
- Start from the current default pack as a baseline.
- Edit only the sections you want to customize.
- Validate JSON parsing and field shapes.
- Host the JSON at an HTTPS URL with CORS suitable for browser fetches.
- Give users that URL; they add it in Settings -> Datapack Source -> Add Custom URL.
Cache and refresh behaviour
There are two cache layers in frontend code:
| Layer | Behaviour |
|---|---|
| Browser HTTP cache | Normal initialization fetches with cache: 'force-cache'. Browser cache storage/eviction is managed by the browser. |
| Datapack manager memory maps | Parsed players/clubs/leagues/cups/stadiums live in singleton Map objects for fast lookup during the session. |
datapackManager.refresh() clears in-memory maps, fetches with cache: 'reload', rebuilds maps, and the Settings modal reloads the page after success. datapackManager.clearCache() clears only the in-memory maps and initialization flag; the code comments explicitly note that HTTP cache is managed by the browser.
getCacheInfo() reports:
{
"type": "HTTP Cache",
"note": "Cache managed by browser. Use force refresh to bypass cache.",
"inMemory": {
"players": 171376,
"clubs": 5350,
"leagues": 322,
"cups": 159,
"stadiums": 5350
}
}
The counts above reflect the current default pack and will change as the pack is updated.
Backend/updater import behaviour
The backend updater can import the datapack into name tables for Datacentre use. This is a service-side operation:
requests.get(DATAPACK_URL, timeout=15)downloads the JSON.- Missing
PackDataraisesValueError. - The updater creates/updates these tables:
dc_player_names,dc_club_names,dc_league_names,dc_cup_names,dc_venue_names. - Rows are inserted with
INSERT ... ON DUPLICATE KEY UPDATE, so re-importing updates existing names/colours. - Removed rows are not deleted automatically.
Notes
LeagueData.dis 1-based, while some API responses use 0-based divisions. Frontend adds1before looking up league names.ClubData.baseImageUrl = "#"is meaningful: it tells the frontend to use shirt assets fromNEXT_PUBLIC_CLUB_SHIRT_BASE_URL, not to append club IDs to#.- Name decoding in frontend replaces
~with a space and`with an apostrophe for league/cup/stadium names. - Browser
clear cacheUI only clears the datapack manager memory maps. A full browser HTTP-cache clear is outside this code path. - The server-side datapack manager currently marks itself initialized even after a failed fetch to avoid retry loops; browser datapack manager does not.
- The updater import is merge/update only. If a row disappears from a custom pack, old database rows can remain until manually removed.
- The datapack has no explicit version field. Use URL versioning, file hashing, or release notes if you need cache-busting/auditability for custom packs.