Skip to main content

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.

ShapeMethodsUse it for
SearchSearchModsBrowse grids, a search box, category listings, author pages. Paginated, filterable, sorted
Lookup by idGetMods, GetMod, GetFiles, MatchPlatformFilesResolving ids you already hold into full models
HighlightsGetModsHighlightsV3A 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.

info

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 ICFCoreApiBlueprint nodeReturns
SearchModsSearch Mods InfoTArray<FCFCoreMod> plus FCFCoreApiResponsePagination
GetMods(const TArray<int64>&, ...)Get Mods Info By IdsTArray<FCFCoreMod>
GetMods(const FCFCoreGetModsFilter&, ...)none, C++ onlyTArray<FCFCoreMod>
GetModGet Mod Info By IdFCFCoreMod
GetModDescriptionGet Mod Description By IdFString
GetFilesGet Files Info By IdsTArray<FFile>
MatchPlatformFilesMatch Platform Files By IdsTArray<FFile>
GetModFileChangelogGet Mod File ChangelogFString
GetCategoriesGet Categories InfoTArray<FCategory>
GetVersionTypesGet Version Types InfoTArray<FGameVersionType>
GetVersionsGet Versions InfoTArray<FGameVersionsByType>
GetVersionsDetailednone, C++ onlyTArray<FGameVersionsDetailedByType>
GetModsHighlightsV3Get Mods Highlights V3FModsHighlightsV3
GetGameGet Game InfoFGame
warning

GetVersionsDetailed and the FCFCoreGetModsFilter overload of GetMods have no Blueprint node. A Blueprint-only project needs a thin C++ wrapper to reach them.

note

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.

danger

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.

warning

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

  1. 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.
  2. Fill an FCFCoreApiRequestPagination with the page you want.
  3. Call SearchMods and wait for FSearchModsDelegate.
  4. 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.

FieldType / defaultSent whenMeaning
classIdint32, 0greater than 0One class (the grouping above categories)
categoryIdint32, 0greater than 0A single category
categoryIdsTArray<int32>, emptyarray non-emptySeveral categories, sent as a bracketed comma separated list
gameVersionFString, emptystring non-emptyA game version string, as returned by GetVersions
searchFilterFString, emptystring non-emptyFree text query. The SDK URL-encodes it for you
sortFieldECFCoreModsSearchSortField, Nonenot NoneWhich field to sort on
sortOrderECFCoreSortOrder, Descnot NoneAscending or descending
modLoaderTypeECFCoreModLoaderType, Anynot AnyOne mod loader
gameVersionTypeIdint32, 0greater than 0A version type, as returned by GetVersionTypes
authorIdint32, 0greater than 0Only mods this author is a member of
primaryAuthorIdint32, 0greater than 0Only mods this author is the owner of
premiumFilterTypeECFCorePremiumFilterType, FreeAndPremiumnot FreeAndPremiumFree, premium, or both
modsSearchEnhancedFeaturesint64, 0greater than 0Bitwise mask of ECFCoreModsSearchEnhancedFeatures
warning

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.

warning

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.

note

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.

ValueNumericBlueprint display nameSorts by
None0NoneNo sort field sent, server default applies
Featured1FeaturedFeatured plus popularity
Popularity2PopularityPopularity
LastUpdated3Last UpdatedLast file uploaded for the mod, newest first
Name4NameMod name
Author5AuthorAuthor
TotalDownloads6Total InstallsDownload count
Category7CategoryCategory
GameVersion8Game VersionGame version
EarlyAccess9Early AccessMods with early access files first
FeaturedReleased10Featured ReleasedFeatured plus mod released date, newest first
ReleasedDate11Released DateMod released date
Rating12RatingRating
note

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

ECFCoreSortOrderNumericEffect
None0No sort order sent, server default applies
Asc1Ascending
Desc2Descending

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.

ECFCoreModsSearchEnhancedFeaturesNumericEffect
None0No enhanced features
ExtractModId1If 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.

ECFCorePremiumFilterTypeNumericReturns
FreeAndPremium0Both free and premium mods
PremiumOnly1Premium mods only
FreeOnly2Free 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 fieldType / defaultSent when
indexint32, 0greater than 0
pageSizeint32, 20greater than 0
Response fieldType / defaultMeaning
indexint32, 0The index this page starts at
pageSizeint32, 0The page size the server applied
resultCountint32, 0How many records are in this page
totalCountint32, 0How many records match the filter in total

How it works

  1. Request page zero with your chosen pageSize, leaving index at 0.
  2. Read pageSize and totalCount off the response.
  3. If index + pageSize is less than totalCount, another page exists.
  4. Set the next index to index + pageSize, and repeat. Stop if the response pageSize is 0 or 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.

warning

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.

note

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:

FieldType / defaultMeaning
modIdsTArray<int64>, emptyThe mod ids to fetch
devModIdsTArray<int64>, emptyDevelopment versions of these mods. Servers only
filterPcOnlybool, falseApplies when isServer is true. true returns the latest server mod that has a corresponding Windows mod, otherwise a server mod covering all supported platforms
note

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.

warning

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.

FCategoryType / defaultWhat a UI uses it for
idint64, 0The value you put into the search filter, narrowed to int32
gameIdint64, 0The owning game
nameFString, emptyThe label
slugFString, emptyStable string key for deep links and analytics
urlFString, emptyWeb page for the category
iconUrlFString, emptyIcon beside the label
dateModifiedFDateTime, 0Last change, for cache invalidation
isClassbool, falsetrue when this record is a class, not a category
classIdint64, 0The class this category belongs to
parentCategoryIdint64, 0Parent category, for nesting
displayIndexint32, -1Intended 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.

CallBlueprint nodeReturnsShape
GetVersionTypesGet Version Types InfoTArray<FGameVersionType>id (int64), gameId (int64), name, slug
GetVersionsGet Versions InfoTArray<FGameVersionsByType>type (int64, matching FGameVersionType::id) plus versions (TArray<FString>)
GetVersionsDetailednone, C++ onlyTArray<FGameVersionsDetailedByType>type (int64) plus versions (TArray<FGameVersionDetailed>, each id, name, slug)
warning

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:

FieldTypeMeaning
idint64The game id
nameFStringDisplay name
slugFStringURL safe short name
dateModifiedFDateTimeWhen the game record last changed
assetsFGameAssetsiconUrl, tileUrl and coverUrl
statusECFCoreStatusNone 0, Draft 1, Test 2, PendingReview 3, Rejected 4, Approved 5, Live 6
apiStatusECFCoreApiStatusNone 0, Private 1, Public 2
supportedFeaturesFGameSupportedFeaturesCapability 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.

FieldType / defaultWhat a UI needs it for
idint64, 0Primary key for every follow-up call
gameIdint64, 0Owning game
gamePopularityRankint32, 0Popularity rank within the game
nameFString, emptyTile and page title
slugFString, emptyStable string key for deep links and analytics
linksFModLinksOutbound links
summaryFString, emptyShort description for a tile or list row
statusECFCoreModStatus, NoneModeration and lifecycle state
downloadCountint64, 0Install count for a tile badge
isFeaturedbool, falseWhether the mod is editorially featured
classIdint32, 0Class grouping
primaryCategoryIdint32, 0The category to show as "the" category
categoriesTArray<FCategory>, emptyFull category chips
authorsTArray<FModAuthor>, emptyByline
logoFModAssetTile art
screenshotsTArray<FModAsset>, emptyDetail page gallery
videosTArray<FModAsset>, emptyDetail page media
mainFileIdint64, 0The file id to treat as the primary release
latestFilesTArray<FFile>, emptyFull file records for the newest releases
latestFilesIndexesTArray<FFileIndex>, emptyLightweight per-version index of the newest files
dateCreatedFDateTime, 0Created
dateModifiedFDateTime, 0Last modified
dateReleasedFDateTime, 0Released
allowModDistributionbool, truefalse means third-party distribution is not permitted
isAvailablebool, falseWhether the mod is currently available
ratingDetailsFRatingDetailsRating widget data
premiumDetailsFPremiumDetailsPrice and premium state
warning

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.

note

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.

note

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).

ECFCoreRatingScoreNumericECFCoreRatingScoreNumeric
NotEnoughReviews0Mixed5
OverwhelminglyPositive1MostlyNegative6
VeryPositive2Negative7
Positive3VeryNegative8
MostlyPositive4OverwhelminglyNegative9
note

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.

FPremiumDetailsType / defaultNotes
isPremiumbool, falseWhether the mod is premium
isFreemiumbool, falsePremium but installable without purchase
tierPricefloat, 0The list price
currencySymbolFString, emptySymbol to render with the price
platformDataFPremiumDetailsPlatformDataOne 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
discountDataFPremiumDetailsDiscountActive discount, if any
trialDetailsFPremiumDetailsTrialTry Before You Buy state: isEnabled (bool, false) and allowedHours (int32, 0)
note

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).

ECFCoreModStatusNumericECFCoreModStatusNumeric
None0ChangesMade6
New1Inactive7
ChangesRequired2Abandoned8
UnderSoftReview3Deleted9
Approved4UnderReview10
Rejected5

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.

FieldType / defaultWhat a UI needs it for
idint64, 0File id. 0 is treated as uninstalled or non-existent
gameIdint64, 0Owning game
modIdint64, 0Owning mod
isAvailablebool, falseWhether the file is currently available
displayNameFString, emptyHuman readable version label
fileNameFString, emptyOn-disk file name
releaseTypeECFCoreFileReleaseType, NoneRelease, Beta or Alpha badge
fileStatusECFCoreFileStatus, NonePipeline and moderation state
hashesTArray<FFileHash>, emptyIntegrity checks. Each has value (FString) and algo (ECFCoreHashAlgo: None 0, Sha1 1, Md5 2)
fileDateFDateTime, 0Upload date
fileLengthint64, 0The size of the archive as uploaded, in bytes
fileSizeOnDiskint64, 0Size on disk in bytes
downloadCountint64, 0Per-file install count
downloadUrlFString, emptyDirect download URL where available
gameVersionsTArray<FString>, emptyVersion strings this file targets
sortableGameVersionsTArray<FSortableGameVersion>, emptySortable version records
dependenciesTArray<FFileDependency>, emptyRelated mods and files
ExposeAsAlternativebool, falseWhether this file is exposed as an alternative. Capitalized unlike every other field on this struct
parentProjectFileIdint64, 0Parent file, when this file is a child
alternateFileIdint64, 0The alternate file, when one exists
isServerPackbool, falseWhether this file is a server pack
serverPackFileIdint64, 0The matching server pack file
fileFingerprintint64, 0Fingerprint for matching local content
modulesTArray<FFileModule>, emptyPer-module fingerprints: name (FString) and fingerprint (int64)
cookingInfoFFileCookingInfoCarries cookerVersion (FString), for cloud cooking
warning

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:

FieldType
gameVersionNameFString
gameVersionPaddedFString
gameVersionFString
gameVersionReleaseDateFDateTime
gameVersionTypeIdint32

FFileDependency: modId (int64), fileId (int64) and relationType. ECFCoreFileReleaseType: None 0, Release 1, Beta 2, Alpha 3.

ECFCoreFileRelationTypeNumericECFCoreFileRelationTypeNumeric
None0Tool4
EmbeddedLibrary1Incompatible5
OptionalDependency2Include6
RequiredDependency3
warning

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.

ECFCoreFileStatusNumericECFCoreFileStatusNumeric
None0Archived8
Processing1Testing9
ChangesRequired2Released10
UnderReview3ReadyForReview11
Approved4Deprecated12
Rejected5Baking13
MalwareDetected6AwaitingPublishing14
Deleted7FailedPublishing15
warning

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.

note

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.

info

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.

FieldDefaultReturns
carouseltrueThe manually configured carousel, for promoting mods
bannerstrueManually configured banners
categoriestrueManually configured game categories, for promoting categories
latestfalseThe latest uploaded mods, where a new file was uploaded
mostDownloadedfalseMods ordered by their all-time download count
premiumfalsePremium mods ordered by latest uploaded
trendingfalseMods ordered by trending popularity
themePagesfalseA list of theme page objects. V2 and above only
warning

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.

warning

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

  1. Fill the filter, excluding what the player already owns.
  2. Call GetModsHighlightsV3 and check the error optional.
  3. Render carousel.
  4. Sort shelves on displayIndex, then branch on each shelf's type before reading its content.
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:

FieldType / defaultMeaning
displayIndexint32, MAX_int32Intended position, so a shelf the server left unset sorts last
typeECFCoreShelfType, CategoryWhich kind of shelf, and therefore which content member is populated
nameFString, emptyThe row heading
queryParamsFQueryParamsFor a generated shelf, the exact query that produced its results
contentFShelfContentThe payload
destinationFCarouselItemDestinationWhere the row header or See All navigates to
seeAllButtonbool, falseWhether 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.

FieldTypePopulated when type is
modsTArray<FCFCoreHighlightsMod>Generated or Custom
categoriesTArray<FCategory>Category
bannerFCarouselItemBanner
warning

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.

FQueryParamsType / defaultMaps onto
classIdint32, -1FCFCoreSearchModsFilter::classId
premiumStatusECFCorePremiumFilterType, FreeAndPremiumpremiumFilterType
categoryIdsTArray<int32>, emptycategoryIds
sortingOptionECFCoreModsSearchSortField, LastUpdatedsortField
sortingOrderECFCoreSortOrder, DescsortOrder
note

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:

FieldType
idint64
logoFModAsset
premiumDetailsFPremiumDetails
nameFString
authorsTArray<FModAuthor>
ratingDetailsFRatingDetails
downloadCountint64
fileSizeint64

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:

FieldType / defaultMeaning
mainTitleFString, emptyHeadline
subTitleFString, emptySubhead
mainImageUrlFString, emptyFull size art
tileImageUrlFString, emptyTile sized art
customTagFString, emptyEditorial tag, for example a badge string
ctaTextFString, emptyThe call to action label for the button
destinationFCarouselItemDestinationWhere the item navigates to
layoutECFCoreCarouselItemLayout, DefaultDefault 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).

ECFCoreCarouselItemDestinationTypeNumericvalue contains
None0Nothing to navigate to
Mod1The mod id
ThemePage2The theme page id
warning

Handle None. An item with a None destination should render without a clickable affordance rather than navigating to mod id 0.

FThemePage:

FieldTypeMeaning
idint64Usable as a FCarouselItemDestination value
bannerUrlFStringBanner art
mainTitleFStringHeadline
subTitleFStringSubhead
modIdsTArray<int64>The mods on this theme page
warning

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.

warning

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.