Browsing, searching and discovery
Everything a player sees before they install comes from the read side of the CurseForge API: a search grid, a category tree, a mod detail page, a curated Discover page. This page covers that surface in cfcore-sdk-ue. If you use the shipped UI module instead of building your own screens, see the mod browser.
There are three query shapes, and picking the wrong one is a common first-integration mistake.
| Shape | Methods | Use it for |
|---|---|---|
| Search | SearchMods | Browse grids, a search box, category listings, author pages. Paginated, filterable, sorted |
| Lookup by id | GetMods, GetMod, GetFiles, MatchPlatformFiles | Resolving ids you already hold into full models |
| Highlights | GetModsHighlightsV3 | A Discover or Home surface whose layout your team edits in the Developer Portal |
Reaching the read API
The read surface is ICFCoreApi. In C++ you reach it through the context singleton.
#include <cfcore_context.h>
#include <api/cfcore_api.h>
cfcore::ICFCoreApi* Api = cfcore::CFCoreContext::GetInstance()->Api();
// The token-requiring read calls hang off the same interface:
TSharedPtr<cfcore::ICFCoreApiAuthorized> Authorized = Api->Authorized();
Blueprint: there is no context node. The same operations are functions on the CFCoreSubsystem engine subsystem, so use Get Engine Subsystem with Class set to CFCoreSubsystem and drag off the result. Every node on this page is under the cfcore|Api category, except the two analytics nodes at the end.
The SDK must be initialized. Every cfcore|Api node asserts that first and, if it is not, fires its error delegate with ECFCoreErrorCodes::FailedToInitialize without contacting the server.
The query surface at a glance
C++ method on ICFCoreApi | Blueprint node | Returns |
|---|---|---|
SearchMods | Search Mods Info | TArray<FCFCoreMod> plus FCFCoreApiResponsePagination |
GetMods(const TArray<int64>&, ...) | Get Mods Info By Ids | TArray<FCFCoreMod> |
GetMods(const FCFCoreGetModsFilter&, ...) | none, C++ only | TArray<FCFCoreMod> |
GetMod | Get Mod Info By Id | FCFCoreMod |
GetModDescription | Get Mod Description By Id | FString |
GetFiles | Get Files Info By Ids | TArray<FFile> |
MatchPlatformFiles | Match Platform Files By Ids | TArray<FFile> |
GetModFileChangelog | Get Mod File Changelog | FString |
GetCategories | Get Categories Info | TArray<FCategory> |
GetVersionTypes | Get Version Types Info | TArray<FGameVersionType> |
GetVersions | Get Versions Info | TArray<FGameVersionsByType> |
GetVersionsDetailed | none, C++ only | TArray<FGameVersionsDetailedByType> |
GetModsHighlightsV3 | Get Mods Highlights V3 | FModsHighlightsV3 |
GetGame | Get Game Info | FGame |
GetVersionsDetailed and the FCFCoreGetModsFilter overload of GetMods have no Blueprint node. A Blueprint-only project needs a thin C++ wrapper to reach them.
The Blueprint nodes are not a one-to-one mirror of the C++ signatures. ICFCoreApi::GetFiles takes an FCFCoreGetFilesFilter while Get Files Info By Ids takes a raw TArray<int64> named FileIds, and Match Platform Files By Ids reuses FCFCoreApiGetFilesDelegate rather than the C++ FMatchPlatformFilesDelegate. Read the node's pins, not the header, when working in Blueprint.
ICFCoreApi also declares read calls that belong to install rather than browsing: GetModFileDownloadUrl (which requires an authenticated player for premium mods), GetModFileDeltaDiff and GetActiveCookingVersion. GetDirectDownloadDomain and GetDirectDownloadDomainEx exist for the SDK's own chunked downloading. Do not call them.
Callbacks and errors
Every request method here is asynchronous, returns void, and delivers its result on a delegate declared inside ICFCoreApi carrying the payload as TOptional plus an error TOptional.
Test the error optional first and only then call GetValue() on the payload. On failure the payload optionals are not guaranteed to be set.
The delegate signatures are not uniform: some payloads arrive by value, some by const reference, and FMatchPlatformFilesDelegate alone takes the error optional by value. Copy the declaration you need from cfcore_api.h or your handler will not bind.
FCFCoreApiResponseError is a flag set rather than one code, so branch on the condition you care about. cancelled, badRequest, entityNotFound, serverUnreachable, missingPrivileges, tokenExpired, resourceExpired and failedToParseServerResponse are all bool, default false. It also carries errorCode (int32, default cfcore::api_error_codes::kNone, which is 0) and a server-supplied description.
Blueprint splits the same contract into a results delegate plus a separate on_error or OnError pin bound to FCFCoreErrorDelegate, which carries an FCFCoreError exposing isError, code (ECFCoreErrorCodes), the nested apiError (FCFCoreApiResponseError) and description.
Every cfcore|Api node declares its error pin with AutoCreateRefTerm, so the node compiles with that pin unconnected and then silently discards every failure. Bind it on every discovery node.
Searching the catalogue
How it works
- Fill an
FCFCoreSearchModsFilter. Every field left at its default is omitted from the request, so a default filter is a plain "everything for this game" query. - Fill an
FCFCoreApiRequestPaginationwith the page you want. - Call
SearchModsand wait forFSearchModsDelegate. - Check the error optional, then read the mods array and the returned pagination.
void UMyModBrowser::RunSearch(const FString& InText, int32 InPageIndex) {
FCFCoreSearchModsFilter filter;
filter.searchFilter = InText;
filter.sortField = ECFCoreModsSearchSortField::Popularity;
filter.sortOrder = ECFCoreSortOrder::Desc;
// Bitwise field: if the typed text is a mod id, return that mod.
filter.modsSearchEnhancedFeatures =
static_cast<int64>(ECFCoreModsSearchEnhancedFeatures::ExtractModId);
FCFCoreApiRequestPagination pagination;
pagination.index = InPageIndex;
pagination.pageSize = 20;
cfcore::CFCoreContext::GetInstance()->Api()->SearchMods(
filter, pagination,
ICFCoreApi::FSearchModsDelegate::CreateUObject(
this, &UMyModBrowser::OnSearchModsComplete));
}
void UMyModBrowser::OnSearchModsComplete(
TOptional<TArray<FCFCoreMod>> OptMods,
TOptional<FCFCoreApiResponsePagination> OptPagination,
const TOptional<FCFCoreApiResponseError>& OptError) {
if (OptError.IsSet()) {
UE_LOG(LogTemp, Error, TEXT("%s"), *OptError.GetValue().description);
return;
}
const TArray<FCFCoreMod>& Mods = OptMods.GetValue();
const FCFCoreApiResponsePagination& Page = OptPagination.GetValue();
const bool bHasMorePages = (Page.index + Page.pageSize) < Page.totalCount;
}
Blueprint: the node is Search Mods Info, with pins filter, pagination, on_results (giving you mods and pagination) and on_error.
Search filter fields
FCFCoreSearchModsFilter is a plain USTRUCT, all fields BlueprintReadWrite. The "sent when" column is the serializer's actual behaviour, and it is what makes a default filter safe.
| Field | Type / default | Sent when | Meaning |
|---|---|---|---|
classId | int32, 0 | greater than 0 | One class (the grouping above categories) |
categoryId | int32, 0 | greater than 0 | A single category |
categoryIds | TArray<int32>, empty | array non-empty | Several categories, sent as a bracketed comma separated list |
gameVersion | FString, empty | string non-empty | A game version string, as returned by GetVersions |
searchFilter | FString, empty | string non-empty | Free text query. The SDK URL-encodes it for you |
sortField | ECFCoreModsSearchSortField, None | not None | Which field to sort on |
sortOrder | ECFCoreSortOrder, Desc | not None | Ascending or descending |
modLoaderType | ECFCoreModLoaderType, Any | not Any | One mod loader |
gameVersionTypeId | int32, 0 | greater than 0 | A version type, as returned by GetVersionTypes |
authorId | int32, 0 | greater than 0 | Only mods this author is a member of |
primaryAuthorId | int32, 0 | greater than 0 | Only mods this author is the owner of |
premiumFilterType | ECFCorePremiumFilterType, FreeAndPremium | not FreeAndPremium | Free, premium, or both |
modsSearchEnhancedFeatures | int64, 0 | greater than 0 | Bitwise mask of ECFCoreModsSearchEnhancedFeatures |
authorId and primaryAuthorId are not synonyms. An author page using the wrong one shows the wrong set of mods for any mod with co-authors.
The filter's id fields are narrower than the ids the models give you. classId, categoryId and the elements of categoryIds are int32, but FCategory::id, FCategory::classId and FCategory::parentCategoryId are int64. gameVersionTypeId is int32, but FGameVersionType::id and FGameVersionsByType::type are int64. Narrow explicitly when wiring a category chip or version dropdown into a search.
modsSearchEnhancedFeatures is typed int64 rather than the enum because it is a bitmask. Cast each value to int64 and combine with bitwise OR, so future flags can be added without breaking the field's type.
Sort fields
ECFCoreModsSearchSortField carries UMETA display names, which are the labels a Blueprint dropdown shows.
| Value | Numeric | Blueprint display name | Sorts by |
|---|---|---|---|
None | 0 | None | No sort field sent, server default applies |
Featured | 1 | Featured | Featured plus popularity |
Popularity | 2 | Popularity | Popularity |
LastUpdated | 3 | Last Updated | Last file uploaded for the mod, newest first |
Name | 4 | Name | Mod name |
Author | 5 | Author | Author |
TotalDownloads | 6 | Total Installs | Download count |
Category | 7 | Category | Category |
GameVersion | 8 | Game Version | Game version |
EarlyAccess | 9 | Early Access | Mods with early access files first |
FeaturedReleased | 10 | Featured Released | Featured plus mod released date, newest first |
ReleasedDate | 11 | Released Date | Mod released date |
Rating | 12 | Rating | Rating |
The value is TotalDownloads but its display name is "Total Installs". Use the enum name in C++ and expect the label anywhere you drive UI off UEnum display text.
Sort order, enhanced features, premium filter, mod loader
ECFCoreSortOrder | Numeric | Effect |
|---|---|---|
None | 0 | No sort order sent, server default applies |
Asc | 1 | Ascending |
Desc | 2 | Descending |
This enum carries no UMETA names, so Blueprint shows the values as declared. The struct default is Desc, and because Desc is not None the sort order is sent on every search whether or not you set a sortField.
ECFCoreModsSearchEnhancedFeatures | Numeric | Effect |
|---|---|---|
None | 0 | No enhanced features |
ExtractModId | 1 | If the search text is a mod id, the server tries to return that mod |
ExtractModId is what lets one search box accept both names and pasted mod ids.
ECFCorePremiumFilterType | Numeric | Returns |
|---|---|---|
FreeAndPremium | 0 | Both free and premium mods |
PremiumOnly | 1 | Premium mods only |
FreeOnly | 2 | Free mods only |
ECFCoreModLoaderType: Any 0, Forge 1, Cauldron 2, LiteLoader 3, Fabric 4. Any is the default and is omitted from the request. Leave it there unless your title models mod loaders.
Pagination in and out
SearchMods is the only call on this page that pages. You send an FCFCoreApiRequestPagination and the server reports what it applied in an FCFCoreApiResponsePagination.
| Request field | Type / default | Sent when |
|---|---|---|
index | int32, 0 | greater than 0 |
pageSize | int32, 20 | greater than 0 |
| Response field | Type / default | Meaning |
|---|---|---|
index | int32, 0 | The index this page starts at |
pageSize | int32, 0 | The page size the server applied |
resultCount | int32, 0 | How many records are in this page |
totalCount | int32, 0 | How many records match the filter in total |
How it works
- Request page zero with your chosen
pageSize, leavingindexat0. - Read
pageSizeandtotalCountoff the response. - If
index + pageSizeis less thantotalCount, another page exists. - Set the next
indextoindex + pageSize, and repeat. Stop if the responsepageSizeis0or the computed next index does not advance past the current one, since a non-advancing index means the loop will not terminate on its own.
Blueprint: build the request with the pure make node for FCFCoreApiRequestPagination (UCFCoreBPLibrary::MakeApiRequestPagination, pins index and page_size), then read the pagination struct on the Search Mods Info results delegate.
Do not change the filter between pages. index is an offset into the result set the current filter and sort produce, so changing either mid-scroll shifts the window underneath you. Rebuild from page zero when a filter changes.
The serializer sends whatever positive pageSize you set without clamping, and the SDK documents no upper bound. Confirm the ceiling for your game with cfforstudios@overwolf.com before shipping an interface that depends on a large page.
Looking up mods and files by id
Every id-shaped lookup follows one pattern: pass ids, get full models, check the error first. The SDK does not document the ordering of the returned array relative to your input array, so key results by id, not by index.
FCFCoreGetFilesFilter files_filter;
files_filter.fileIds = { 111111, 222222 };
cfcore::CFCoreContext::GetInstance()->Api()->GetFiles(
files_filter,
ICFCoreApi::FGetFilesDelegate::CreateLambda(
[](const TOptional<TArray<FFile>>& OptFiles,
const TOptional<FCFCoreApiResponseError>& OptError) {
if (OptError.IsSet()) {
return;
}
for (const FFile& File : OptFiles.GetValue()) {
// File.displayName, File.fileLength, File.releaseType ...
}
}));
Blueprint: Get Mods Info By Ids (pins modIds, on_results, on_error), Get Mod Info By Id (modId, on_mod, on_error), Get Files Info By Ids (FileIds, OnResults, OnError), Match Platform Files By Ids (filter, OnResults, OnError), Get Mod Description By Id (modId, on_mod_desc, on_error) and Get Mod File Changelog (ModId, FileId, OnChangelog, OnError).
MatchPlatformFiles is the one whose purpose is not obvious from the name. It takes an array of file ids and returns the FFile objects matching those ids for the current platform, mainly for client-server sessions: the server returns a list of file ids, and the client downloads the files matching its own platform. Its filter, FMatchPlatformFilesFilter, carries one fileIds array of int64.
The C++ only GetMods overload exists for server-side callers. FCFCoreGetModsFilter:
| Field | Type / default | Meaning |
|---|---|---|
modIds | TArray<int64>, empty | The mod ids to fetch |
devModIds | TArray<int64>, empty | Development versions of these mods. Servers only |
filterPcOnly | bool, false | Applies when isServer is true. true returns the latest server mod that has a corresponding Windows mod, otherwise a server mod covering all supported platforms |
The authorized call GetMyMods returns mods without file information. For a mod author's own files, take the ids from Get My Mods and call Get Mods Info By Ids.
Categories, versions and game metadata
Categories give you the filter tree for a browse screen. Classes sit above categories, and a game only has more than one if it was configured that way in the Developer Portal at console.curseforge.com. Call GetCategories with classesOnly set to true to learn the classes, then again per class with classId set. Build the UI from displayIndex and parentCategoryId, then feed the chosen ids into the search filter, narrowing each int64 to int32.
FCFCoreGetCategoriesFilter has two fields: classId (int32, default 0, sent when greater than 0) and classesOnly (bool, default false, sent when true, relevant only if your game has more than one class).
Blueprint: the node is Get Categories Info, with pins filter, on_results and on_error.
The pure make node for this filter (UCFCoreBPLibrary::MakeGetCategoriesFilter) exposes only a class_id pin and cannot set classesOnly. To query classes from Blueprint, set the struct member directly instead of using the make node.
FCategory | Type / default | What a UI uses it for |
|---|---|---|
id | int64, 0 | The value you put into the search filter, narrowed to int32 |
gameId | int64, 0 | The owning game |
name | FString, empty | The label |
slug | FString, empty | Stable string key for deep links and analytics |
url | FString, empty | Web page for the category |
iconUrl | FString, empty | Icon beside the label |
dateModified | FDateTime, 0 | Last change, for cache invalidation |
isClass | bool, false | true when this record is a class, not a category |
classId | int64, 0 | The class this category belongs to |
parentCategoryId | int64, 0 | Parent category, for nesting |
displayIndex | int32, -1 | Intended display order |
Two search filter fields are driven by version data: gameVersion (a string) and gameVersionTypeId (an id). The version taxonomy itself, and the int64 to int32 narrowing it forces, is covered on Game info and platforms.
| Call | Blueprint node | Returns | Shape |
|---|---|---|---|
GetVersionTypes | Get Version Types Info | TArray<FGameVersionType> | id (int64), gameId (int64), name, slug |
GetVersions | Get Versions Info | TArray<FGameVersionsByType> | type (int64, matching FGameVersionType::id) plus versions (TArray<FString>) |
GetVersionsDetailed | none, C++ only | TArray<FGameVersionsDetailedByType> | type (int64) plus versions (TArray<FGameVersionDetailed>, each id, name, slug) |
GetVersions gives you version strings, which is what FCFCoreSearchModsFilter::gameVersion wants. GetVersionsDetailed gives you version ids, which is what the file creation flow wants. A Blueprint-only project needs a C++ wrapper for the detailed call.
GetGame (Blueprint Get Game Info, pins on_game and on_error) returns one FGame for the title the SDK was initialized for:
| Field | Type | Meaning |
|---|---|---|
id | int64 | The game id |
name | FString | Display name |
slug | FString | URL safe short name |
dateModified | FDateTime | When the game record last changed |
assets | FGameAssets | iconUrl, tileUrl and coverUrl |
status | ECFCoreStatus | None 0, Draft 1, Test 2, PendingReview 3, Rejected 4, Approved 5, Live 6 |
apiStatus | ECFCoreApiStatus | None 0, Private 1, Public 2 |
supportedFeatures | FGameSupportedFeatures | Capability flags. One flag today: supportModSubscriptions (bool, default false) |
Gate any subscription affordance on supportModSubscriptions rather than assuming the feature is on.
The mod model
FCFCoreMod is the value type behind every mod tile, row and detail page. All fields are BlueprintReadOnly.
| Field | Type / default | What a UI needs it for |
|---|---|---|
id | int64, 0 | Primary key for every follow-up call |
gameId | int64, 0 | Owning game |
gamePopularityRank | int32, 0 | Popularity rank within the game |
name | FString, empty | Tile and page title |
slug | FString, empty | Stable string key for deep links and analytics |
links | FModLinks | Outbound links |
summary | FString, empty | Short description for a tile or list row |
status | ECFCoreModStatus, None | Moderation and lifecycle state |
downloadCount | int64, 0 | Install count for a tile badge |
isFeatured | bool, false | Whether the mod is editorially featured |
classId | int32, 0 | Class grouping |
primaryCategoryId | int32, 0 | The category to show as "the" category |
categories | TArray<FCategory>, empty | Full category chips |
authors | TArray<FModAuthor>, empty | Byline |
logo | FModAsset | Tile art |
screenshots | TArray<FModAsset>, empty | Detail page gallery |
videos | TArray<FModAsset>, empty | Detail page media |
mainFileId | int64, 0 | The file id to treat as the primary release |
latestFiles | TArray<FFile>, empty | Full file records for the newest releases |
latestFilesIndexes | TArray<FFileIndex>, empty | Lightweight per-version index of the newest files |
dateCreated | FDateTime, 0 | Created |
dateModified | FDateTime, 0 | Last modified |
dateReleased | FDateTime, 0 | Released |
allowModDistribution | bool, true | false means third-party distribution is not permitted |
isAvailable | bool, false | Whether the mod is currently available |
ratingDetails | FRatingDetails | Rating widget data |
premiumDetails | FPremiumDetails | Price and premium state |
Both allowModDistribution and isAvailable arrive per mod from the server. Check both before you show an install affordance, or you will offer installs that cannot complete.
latestFiles is the full FFile record, useful on a detail page that wants size, hashes and dependencies. latestFilesIndexes is FFileIndex, carrying only gameVersion, fileId, fileName, releaseType, gameVersionTypeId and modLoader, which is the cheaper structure for a version picker.
FModAsset (used by logo, screenshots and videos) carries id (int64), modId (int64), title, description, thumbnailUrl for grids and lists, and url for full size. FModAuthor carries id (int64), name and url.
FModLinks, all FString: websiteUrl, wikiUrl, issuesUrl, sourceUrl and modManagementUrl.
modManagementUrl is only filled in on the CreateMod and GetMyMods responses, so it is empty on search and lookup results. Do not build a management button off a search response.
FRatingDetails carries rating (float, 0), totalRatings (int64, 0), positiveRatings (int64, 0) and score (ECFCoreRatingScore, default NotEnoughReviews).
ECFCoreRatingScore | Numeric | ECFCoreRatingScore | Numeric | |
|---|---|---|---|---|
NotEnoughReviews | 0 | Mixed | 5 | |
OverwhelminglyPositive | 1 | MostlyNegative | 6 | |
VeryPositive | 2 | Negative | 7 | |
Positive | 3 | VeryNegative | 8 | |
MostlyPositive | 4 | OverwhelminglyNegative | 9 |
Rating availability depends on setup with the platform provider for most platforms, so confirm rating availability for your game before designing a UI around the banded score. Render NotEnoughReviews as an absence of rating, not as a zero-star result.
FPremiumDetails | Type / default | Notes |
|---|---|---|
isPremium | bool, false | Whether the mod is premium |
isFreemium | bool, false | Premium but installable without purchase |
tierPrice | float, 0 | The list price |
currencySymbol | FString, empty | Symbol to render with the price |
platformData | FPremiumDetailsPlatformData | One field, productId (FString): the product id on the player's current platform, which you can pass to platform APIs for details such as local price |
discountData | FPremiumDetailsDiscount | Active discount, if any |
trialDetails | FPremiumDetailsTrial | Try Before You Buy state: isEnabled (bool, false) and allowedHours (int32, 0) |
isFreemium is not a synonym for free. A freemium mod is a premium mod that can be installed without purchasing; the game then queries which mods the player has purchased and unlocks features accordingly. A browse tile should treat freemium as installable while still showing the price.
FPremiumDetailsDiscount is the data behind a sale badge or a SaleView layout: discountPrice (float, 0, where 0 means no discount), percent (float, equal to (discountPrice / tierPrice) * 100), endDate (FDateTime, default FDateTime::MinValue(), the UTC end date after which purchasing with the discounted product id will fail) and platformData (FPremiumDetailsPlatformData, the product id on the relevant non-Windows platform).
ECFCoreModStatus | Numeric | ECFCoreModStatus | Numeric | |
|---|---|---|---|---|
None | 0 | ChangesMade | 6 | |
New | 1 | Inactive | 7 | |
ChangesRequired | 2 | Abandoned | 8 | |
UnderSoftReview | 3 | Deleted | 9 | |
Approved | 4 | UnderReview | 10 | |
Rejected | 5 |
See moderation for what drives those states.
The file model
FFile is what a version picker, a size estimate and an update prompt read from.
| Field | Type / default | What a UI needs it for |
|---|---|---|
id | int64, 0 | File id. 0 is treated as uninstalled or non-existent |
gameId | int64, 0 | Owning game |
modId | int64, 0 | Owning mod |
isAvailable | bool, false | Whether the file is currently available |
displayName | FString, empty | Human readable version label |
fileName | FString, empty | On-disk file name |
releaseType | ECFCoreFileReleaseType, None | Release, Beta or Alpha badge |
fileStatus | ECFCoreFileStatus, None | Pipeline and moderation state |
hashes | TArray<FFileHash>, empty | Integrity checks. Each has value (FString) and algo (ECFCoreHashAlgo: None 0, Sha1 1, Md5 2) |
fileDate | FDateTime, 0 | Upload date |
fileLength | int64, 0 | The size of the archive as uploaded, in bytes |
fileSizeOnDisk | int64, 0 | Size on disk in bytes |
downloadCount | int64, 0 | Per-file install count |
downloadUrl | FString, empty | Direct download URL where available |
gameVersions | TArray<FString>, empty | Version strings this file targets |
sortableGameVersions | TArray<FSortableGameVersion>, empty | Sortable version records |
dependencies | TArray<FFileDependency>, empty | Related mods and files |
ExposeAsAlternative | bool, false | Whether this file is exposed as an alternative. Capitalized unlike every other field on this struct |
parentProjectFileId | int64, 0 | Parent file, when this file is a child |
alternateFileId | int64, 0 | The alternate file, when one exists |
isServerPack | bool, false | Whether this file is a server pack |
serverPackFileId | int64, 0 | The matching server pack file |
fileFingerprint | int64, 0 | Fingerprint for matching local content |
modules | TArray<FFileModule>, empty | Per-module fingerprints: name (FString) and fingerprint (int64) |
cookingInfo | FFileCookingInfo | Carries cookerVersion (FString), for cloud cooking |
fileLength and fileSizeOnDisk are different measurements. fileLength is the archive as delivered; fileSizeOnDisk is the installed, extracted footprint. Prefer fileSizeOnDisk for a free space check so you do not under-reserve.
FSortableGameVersion:
| Field | Type |
|---|---|
gameVersionName | FString |
gameVersionPadded | FString |
gameVersion | FString |
gameVersionReleaseDate | FDateTime |
gameVersionTypeId | int32 |
FFileDependency: modId (int64), fileId (int64) and relationType. ECFCoreFileReleaseType: None 0, Release 1, Beta 2, Alpha 3.
ECFCoreFileRelationType | Numeric | ECFCoreFileRelationType | Numeric | |
|---|---|---|---|---|
None | 0 | Tool | 4 | |
EmbeddedLibrary | 1 | Incompatible | 5 | |
OptionalDependency | 2 | Include | 6 | |
RequiredDependency | 3 |
A detail page that renders every dependency identically is misleading. RequiredDependency and OptionalDependency are prompts to install something else, while Incompatible is a warning not to. The SDK resolves none of this for you: see Mod dependencies for the resolver you have to write.
ECFCoreFileStatus | Numeric | ECFCoreFileStatus | Numeric | |
|---|---|---|---|---|
None | 0 | Archived | 8 | |
Processing | 1 | Testing | 9 | |
ChangesRequired | 2 | Released | 10 | |
UnderReview | 3 | ReadyForReview | 11 | |
Approved | 4 | Deprecated | 12 | |
Rejected | 5 | Baking | 13 | |
MalwareDetected | 6 | AwaitingPublishing | 14 | |
Deleted | 7 | FailedPublishing | 15 |
The SDK does not classify these states for you, and several describe a file that is not installable. Decide explicitly which values your version dropdown accepts, and pair that check with isAvailable.
Both size fields are raw bytes. Two static helpers on UCFCoreBPLibrary format them: static FString FormatFileSize(int64 Bytes) returns a B, KB, MB or GB string, and static FCFCoreFileSize BreakFileSize(int64 Bytes) returns an FCFCoreFileSize, a struct with three int32 fields, kb, mb and gb, each the input size truncated to that unit. Both are BlueprintPure under the cfcore|Utility category, with a single Bytes pin.
Building a Discover surface with highlights
Search answers "find me this". Highlights answers "show me what is worth playing". GetModsHighlightsV3 returns a whole page structure your team configures in the Developer Portal, so a Discover screen can be reshaped without a game update. Content curation tools owns the definitions of carousel, shelf, categories, banner and themed pages, and is where your team configures them. This section is the Unreal side.
The shipped mod browser ships a complete highlights-driven Discover screen built on this same API (carousel, shelves, category tiles, spotlight banners), so a studio adopting that UI does not need to build this section from scratch. What follows is for a studio building its own Discover screen, or extending the shipped one.
Highlights require enablement for your game in the Developer Portal at console.curseforge.com. Do not assume an unconfigured game returns an empty-but-successful response. Bind the error path on your Discover load and confirm enablement with cfforstudios@overwolf.com before you ship the screen. This is not just an abstract runtime-discovery concern: if you use the shipped mod browser, its Discover screen calls this same API, so enablement is a hard prerequisite for that screen to show anything.
Which version to call
Use GetModsHighlightsV3 (Blueprint Get Mods Highlights V3, endpoint /v3/mods/highlights, response FModsHighlightsV3). It is the only version that returns the generalised shelves array in place of a fixed set of named arrays, and the only one with editorial carousel items carrying a title, image, CTA text and destination.
GetModsHighlightsV2 and GetModsHighlights are deprecated. If you are migrating off either, request migration details from cfforstudios@overwolf.com rather than building against a deprecated call.
The highlights filter
All three versions take the same filter, FCFCoreGetModsHighlightsFilter: excludedModIds (TArray<int64>, mod ids to remove from the results) and filters (FCFCoreGetModsHighlightsFilters). Pass the player's installed or owned mod ids in excludedModIds so Discover does not recommend content they already have.
FCFCoreGetModsHighlightsFilters is a set of booleans. Each one, set to true, returns an additional list of mods, each list capped at 10 items.
| Field | Default | Returns |
|---|---|---|
carousel | true | The manually configured carousel, for promoting mods |
banners | true | Manually configured banners |
categories | true | Manually configured game categories, for promoting categories |
latest | false | The latest uploaded mods, where a new file was uploaded |
mostDownloaded | false | Mods ordered by their all-time download count |
premium | false | Premium mods ordered by latest uploaded |
trending | false | Mods ordered by trending popularity |
themePages | false | A list of theme page objects. V2 and above only |
Three default to true and five default to false, so a default-constructed filter requests carousel, banners and categories only. If your Trending or Most Downloaded row renders empty, check the boolean before you suspect the portal configuration.
Each returned list is capped at 10 items. Do not build a shelf that expects more, and do not use highlights to page through a catalogue. That is what SearchMods is for.
The V3 response
How it works
- Fill the filter, excluding what the player already owns.
- Call
GetModsHighlightsV3and check the error optional. - Render
carousel. - Sort
shelvesondisplayIndex, then branch on each shelf'stypebefore reading itscontent.
void UMyDiscoverScreen::LoadDiscover(const TArray<int64>& InInstalledModIds) {
FCFCoreGetModsHighlightsFilter filter;
filter.excludedModIds = InInstalledModIds;
filter.filters.trending = true;
filter.filters.mostDownloaded = true;
filter.filters.themePages = true;
cfcore::CFCoreContext::GetInstance()->Api()->GetModsHighlightsV3(
filter,
ICFCoreApi::FGetModsHighlightsV3Delegate::CreateUObject(
this, &UMyDiscoverScreen::OnHighlightsComplete));
}
void UMyDiscoverScreen::OnHighlightsComplete(
TOptional<FModsHighlightsV3> OptHighlights,
const TOptional<FCFCoreApiResponseError>& OptError) {
if (OptError.IsSet()) {
return;
}
const FModsHighlightsV3& Highlights = OptHighlights.GetValue();
for (const FCarouselItem& Item : Highlights.carousel) {
// Item.mainTitle, Item.mainImageUrl, Item.ctaText, Item.destination
}
// The SDK does not sort shelves. Sort on displayIndex before building widgets.
TArray<FShelf> Shelves = Highlights.shelves;
Shelves.Sort([](const FShelf& A, const FShelf& B) {
return A.displayIndex < B.displayIndex;
});
for (const FShelf& Shelf : Shelves) {
switch (Shelf.type) {
case ECFCoreShelfType::Category: // Shelf.content.categories
break;
case ECFCoreShelfType::Generated:
case ECFCoreShelfType::Custom: // Shelf.content.mods
break;
case ECFCoreShelfType::Banner: // Shelf.content.banner
break;
default:
break;
}
}
}
Blueprint: the node is Get Mods Highlights V3, with pins InFilter, OnResults (giving you a mods value of type FModsHighlightsV3) and OnError. There is no make node for the highlights filter, so break the struct and set excludedModIds and the members of filters directly.
FModsHighlightsV3 has three fields: carousel (TArray<FCarouselItem>), shelves (TArray<FShelf>) and themePages (TArray<FThemePage>).
FShelf is either a single item, such as a banner, or a collection of items, such as mods or categories:
| Field | Type / default | Meaning |
|---|---|---|
displayIndex | int32, MAX_int32 | Intended position, so a shelf the server left unset sorts last |
type | ECFCoreShelfType, Category | Which kind of shelf, and therefore which content member is populated |
name | FString, empty | The row heading |
queryParams | FQueryParams | For a generated shelf, the exact query that produced its results |
content | FShelfContent | The payload |
destination | FCarouselItemDestination | Where the row header or See All navigates to |
seeAllButton | bool, false | Whether to render a See All affordance |
ECFCoreShelfType: Category 0, Generated 1, Custom 2, Banner 3. The SDK applies no sorting of its own, so shelf order as received is whatever the server sent. Sort on displayIndex.
FShelfContent is a tagged union in struct form: exactly one of its three properties is populated, depending on the shelf's type.
| Field | Type | Populated when type is |
|---|---|---|
mods | TArray<FCFCoreHighlightsMod> | Generated or Custom |
categories | TArray<FCategory> | Category |
banner | FCarouselItem | Banner |
Only one of the three is populated per shelf, so always branch on type first. Reading content.mods on a Category shelf yields an empty array, which renders an empty row rather than raising an error.
queryParams is what makes a See All button correct: copy its values into an FCFCoreSearchModsFilter and call SearchMods to open a full, pageable version of the same row.
FQueryParams | Type / default | Maps onto |
|---|---|---|
classId | int32, -1 | FCFCoreSearchModsFilter::classId |
premiumStatus | ECFCorePremiumFilterType, FreeAndPremium | premiumFilterType |
categoryIds | TArray<int32>, empty | categoryIds |
sortingOption | ECFCoreModsSearchSortField, LastUpdated | sortField |
sortingOrder | ECFCoreSortOrder, Desc | sortOrder |
FQueryParams::classId defaults to -1 while the search filter's classId defaults to 0 and is only sent when greater than 0. Treat any non-positive shelf classId as "no class filter" and leave the search filter at 0.
FCFCoreHighlightsMod is thinner than FCFCoreMod, because a shelf tile needs less:
| Field | Type |
|---|---|
id | int64 |
logo | FModAsset |
premiumDetails | FPremiumDetails |
name | FString |
authors | TArray<FModAuthor> |
ratingDetails | FRatingDetails |
downloadCount | int64 |
fileSize | int64 |
It adds fileSize, which FCFCoreMod does not have, and omits summary, categories, latestFiles and status. If your tile needs more than those eight fields, take the id and call GetMods.
FCarouselItem, used by the top-level carousel and by Banner shelves:
| Field | Type / default | Meaning |
|---|---|---|
mainTitle | FString, empty | Headline |
subTitle | FString, empty | Subhead |
mainImageUrl | FString, empty | Full size art |
tileImageUrl | FString, empty | Tile sized art |
customTag | FString, empty | Editorial tag, for example a badge string |
ctaText | FString, empty | The call to action label for the button |
destination | FCarouselItemDestination | Where the item navigates to |
layout | ECFCoreCarouselItemLayout, Default | Default 0, SaleView 1, SmallSaleView 2 |
Treat layout as a contract with the portal and implement all three, because the value is chosen editorially rather than in code. The two sale layouts pair with FPremiumDetails::discountData.
FCarouselItemDestination carries a type of ECFCoreCarouselItemDestinationType (default None) and an int64 value (default 0).
ECFCoreCarouselItemDestinationType | Numeric | value contains |
|---|---|---|
None | 0 | Nothing to navigate to |
Mod | 1 | The mod id |
ThemePage | 2 | The theme page id |
Handle None. An item with a None destination should render without a clickable affordance rather than navigating to mod id 0.
FThemePage:
| Field | Type | Meaning |
|---|---|---|
id | int64 | Usable as a FCarouselItemDestination value |
bannerUrl | FString | Banner art |
mainTitle | FString | Headline |
subTitle | FString | Subhead |
modIds | TArray<int64> | The mods on this theme page |
A themed page gives you modIds, not mod models. Opening one is a two-step flow: read modIds, then call GetMods with them. Put that second call in your navigation, not in your initial highlights load, or you will fetch every themed page's contents on every home screen open.
Reporting discovery analytics
Two calls on ICFCoreAnalytics measure this funnel, both returning bool and both taking an FModBrowsingFunnelParams: SendModBrowsingFunnelImpression when a surface or item becomes visible, and SendModBrowsingFunnelAction when the player acts. Analytics data collection owns the param struct reference, and dashboards covers where the numbers surface.
If you build your own Discover or browse screens, call these directly. If you use the shipped mod browser, its UCFCoreUISubsystem exposes the same two calls and its widgets already emit them as part of normal browsing, so you do not need to wire funnel reporting yourself for that UI.
Three things are specific to a discovery surface. Leave numeric params you do not have at their default: fields set to CF_INVALID_NUMBER (-1, defined in common/defines.h) are ignored rather than sent. Pass shelfName straight from FShelf::name and pageId from FThemePage::id rather than inventing identifiers, so portal-side reporting lines up with the layout your team configured.
Blueprint: the nodes are Send Mod Browsing Funnel Impression Analytic and Send Mod Browsing Funnel Action Analytic, both under cfcore|Analytics, each with pins InParams, OnSuccess and OnError.
Unlike the cfcore|Api nodes, these two do not declare AutoCreateRefTerm, so treat both delegate pins as required inputs.
Where to get help
For anything the SDK does not answer, including the server-side page size ceiling, enablement of highlights or ratings for your game, and per-game category and class configuration, use the Developer Portal at console.curseforge.com and contact cfforstudios@overwolf.com.