Ratings, reporting and blocking
Players do more with your catalog than install mods. They vote mods up or down, report content that breaks the rules, and refuse to see specific mods or specific modded servers again. This page covers those actions in cfcore-sdk-ue. Of the three, the shipped mod browser wires up ratings for you. Reporting is a handoff to CurseForge's own web pages rather than the in-game report call this page also documents, and blocking is not wired up in the shipped UI at all. See Mod browser.
Prerequisites and where these calls live
The SDK must be initialized and the player must be signed in. ICFCore::IsInitialized() reports the first, ICFCore::Authentication()->IsAuthenticated() reports the second. IsAuthenticated() is a local check: it reports whether a non-empty token is stored, not whether the backend still accepts it. See Authentication for concepts and the provider matrix.
Every method on this page lives on cfcore::ICFCoreApiAuthorized, reached through ICFCoreApi::Authorized(). They are not on ICFCoreApi and not on ICFCoreApiAuthentication.
#include "cfcore_context.h"
#include <api/cfcore_api.h>
#include <api/cfcore_api_authorized.h>
using namespace cfcore;
TSharedPtr<ICFCoreApiAuthorized> GetAuthorizedApi() {
ICFCore* Core = CFCoreContext::GetInstance();
if (!Core->IsInitialized() || !Core->Authentication()->IsAuthenticated()) {
return nullptr;
}
return Core->Api()->Authorized();
}
The Blueprint layer flattens that chain: every method below is a node on UCFCoreSubsystem under the palette category cfcore|Api Authorized.
The category is not a reliable index of that interface, since other ICFCoreApiAuthorized methods sit elsewhere, for example the premium mod calls under cfcore|Premium Mods. To gate a graph, call Is Authenticated (category cfcore|Authentication, delegate pin on_is_auth carrying is_authenticated). It returns false when the SDK is not initialized, so one check covers both prerequisites. The nodes below assert initialization and fire their error pin with ECFCoreErrorCodes::FailedToInitialize, but none checks authentication locally: an unauthenticated call is sent and fails at the backend.
| Purpose | C++ (ICFCoreApiAuthorized) | Blueprint node |
|---|---|---|
| Read the signed-in profile | GetMe | Get Me |
| Read mods the player created or is a member of | GetMyMods | Get My Mods |
| Read the player's own votes | GetMyRatings | Get My Ratings |
| Set an up vote or a down vote | UpdateRating | Update Mod Rating |
| Clear the player's vote | RemoveRating | Remove Mod Rating |
| Fetch valid report reasons | GetReportingReasons | Get Reporting Reasons |
| Submit a report against a mod | ReportMod | Report Mod |
| Mint a temporary token for CurseForge web pages | GenerateTempToken | Generate Temp User Token |
| Read what the player has blocked | GetBlockedDetails | Get Blocked Mods Details |
| Unblock mods, authors or servers | UnblockMods | Unblock Mods |
Every C++ method here is asynchronous and takes a delegate last: an optional payload plus an optional FCFCoreApiResponseError. UpdateRating, RemoveRating, ReportMod and UnblockMods return no payload, so an unset error is the success signal. Every Blueprint node's on_error pin is declared with AutoCreateRefTerm, so leaving it unbound compiles and silently drops failures.
Reading the signed-in player
GetMe returns the authenticated account as an FMe: the account chip in your browser, the input to account-linking prompts, and the phasing flags that gate SDK behaviour for that account. There is no change notification, so cache it per session and re-call after a sign-out and sign-in. Every field is BlueprintReadOnly.
FMe field | Type | Notes |
|---|---|---|
id | int64 | CurseForge user id. Defaults to 0. |
displayName | FString | Name to render in UI. |
username | FString | Account username. |
email | FString | Account email. Treat as personal data. |
avatarUrl | FString | URL to the account avatar image. |
dateCreated | FDateTime | Account creation time. |
hasConnectedAccount | bool | Whether an external account is connected. Defaults to false. |
phasingData | FMePhasingData | Feature flag and A/B percentages for this account. |
eulaLastAgreed | FDateTime | When the account last agreed to the EULA. |
diagnostics | bool | Whether diagnostics are on for this account. Defaults to false. |
FMePhasingData is read-only. Every property is an int32 holding 0 to 100, where 0 means disabled for all and 100 means enabled for all, and the value in between is a rollout percentage.
FMePhasingData field | Default | Rollout percentage for |
|---|---|---|
deltaDiffs | 0 | Delta diffs |
analyticsUserEngagement | 100 | User engagement analytics |
analyticsPerfAndStability | 100 | Performance and stability analytics |
modFileSizeValidation | 0 | Mod file size validation |
deleteStaleModDirs | 100 | Stale mod directory cleanup |
deleteStaleTbybMods | 0 | Stale Try Before You Buy mod cleanup |
resumableDownloads | 100 | Resumable downloads |
useTranslations | 0 | Translations |
// The delegate shape every method on this page follows.
Authorized->GetMe(ICFCoreApiAuthorized::FGetMeDelegate::CreateWeakLambda(this,
[this](const TOptional<FMe>& Me,
const TOptional<FCFCoreApiResponseError>& Error) {
if (Error.IsSet() || !Me.IsSet()) {
HandleAuthorizedApiError(Error);
return;
}
CachedUserId = Me.GetValue().id;
bTranslationsEnabled = Me.GetValue().phasingData.useTranslations > 0;
}));
Blueprint: Get Me, pins on_results (output me, type FMe) and on_error (output error, type FCFCoreError).
Mods the player authored
GetMyMods returns every mod for the running game that the signed-in player created or is a member of, as a TArray<FCFCoreMod>. This is the call behind a "My mods" tab. The scope is the running game, not the player's whole CurseForge portfolio.
The returned objects carry information about the mods, not about mod files, so do not trust latestFiles, latestFilesIndexes or mainFileId here. For file details, collect the id values and call ICFCoreApi::GetMods, which is on the plain API interface, not the authorized one, and whose Blueprint node is Get Mods Info By Ids under cfcore|Api.
One field goes the other way: FModLinks::modManagementUrl is only filled in by CreateMod and GetMyMods, so this is where you get the per-mod URL that deep-links an author into the Developer Portal at console.curseforge.com.
Blueprint: Get My Mods, pins on_results (output mods, array of FCFCoreMod) and on_error. The file caveat applies in Blueprint too: do not read Latest Files off these structs.
Rating a mod
Ratings are two-sided, and the two sides share no fields. The aggregate rating arrives on the mod object and needs no authentication. The player's own vote lives on the authorized API and is what your thumb buttons must reflect.
The aggregate side
FCFCoreMod::ratingDetails is an FRatingDetails on every FCFCoreMod, including search results, so a mod card can show community sentiment to a signed-out player.
FRatingDetails field | Type | Meaning |
|---|---|---|
rating | float | Numeric aggregate rating. Defaults to 0. |
totalRatings | int64 | Total number of ratings cast. Defaults to 0. |
positiveRatings | int64 | Number of those that were positive. Defaults to 0. |
score | ECFCoreRatingScore | Bucketed sentiment label. Defaults to NotEnoughReviews. |
Render score rather than computing your own bands, so your UI agrees with the CurseForge website.
ECFCoreRatingScore value | Numeric |
|---|---|
NotEnoughReviews | 0 |
OverwhelminglyPositive | 1 |
VeryPositive | 2 |
Positive | 3 |
MostlyPositive | 4 |
Mixed | 5 |
MostlyNegative | 6 |
Negative | 7 |
VeryNegative | 8 |
OverwhelminglyNegative | 9 |
Most providers require setup on the Developer Portal before ratings work, and the backend's behavior before that setup is done is not documented. Confirm the live state for your game with cfforstudios@overwolf.com before building UI that depends on it.
Blueprint: no node needed. Break any FCFCoreMod, reach Rating Details, then break that for Rating, Total Ratings, Positive Ratings and Score.
The player's own votes
GetMyRatings returns FMyRatings, which carries no aggregate numbers. It answers one question: which mods this player voted on, and in which direction. A mod id in neither array means no vote.
FMyRatings field | Type | Meaning |
|---|---|---|
upvotes | TArray<int64> | Mod ids in the current game that the player up voted. |
downvotes | TArray<int64> | Mod ids in the current game that the player down voted. |
There is no per-mod "did I vote on this" call. Call GetMyRatings once when the browser opens, build two lookup sets, then mutate those sets after each successful vote instead of re-fetching.
The shipped mod browser already does this for you. UCFCoreUISubsystem exposes UpdateAllModRatings (caches ratings once, on demand) and UpdateModRating(int64 ModId, ECFCoreRatingVoteDirection Vote) (looks up the current direction from the cached UserRatings and routes to update or remove accordingly, including the press-the-highlighted-thumb-again-to-undo gesture described below). If you use that UI, you do not need to build the vote flow in this section yourself.
Blueprint: Get My Ratings, pins on_results (output ratings, type FMyRatings) and on_error. The response carries both the up-voted and down-voted arrays.
Setting and clearing a vote
UpdateRating takes the mod id, an ECFCoreRatingVoteDirection and a delegate. It is also how a player changes their mind: call it again with the other direction. There is no separate "change vote" method.
ECFCoreRatingVoteDirection value | Numeric | What to use it for |
|---|---|---|
None | 0 | The "no vote" state in your own UI logic only. See the warning below. |
UpVote | 1 | Register a positive vote for the mod. |
DownVote | 2 | Register a negative vote for the mod. |
UpdateRating does not treat None as "clear my vote". The implementation selects the down vote action only when the direction equals DownVote, and sends the up vote action for every other value, None included. Passing None records an up vote. To clear a vote, call RemoveRating, which takes the mod id and a delegate. Wire it to the "press the highlighted thumb again to undo" gesture.
How it works
- The player presses thumbs-up, thumbs-down, or the already-highlighted thumb.
- Call
UpdateRatingwith the mod id and direction, orRemoveRatingfor an undo. - The delegate carries only an optional error. Unset means the vote was recorded.
- On success update your local vote sets. On failure roll the UI back.
void UMyModBrowser::CastVote(int64 ModId, ECFCoreRatingVoteDirection Direction) {
TSharedPtr<ICFCoreApiAuthorized> Authorized = GetAuthorizedApi();
if (!Authorized.IsValid()) {
return;
}
// Never pass None here: it is sent as an up vote.
if (Direction == ECFCoreRatingVoteDirection::None) {
ClearVote(ModId); // calls Authorized->RemoveRating(ModId, ...)
return;
}
Authorized->UpdateRating(ModId, Direction,
ICFCoreApiAuthorized::FUpdateRatingDelegate::CreateWeakLambda(this,
[this, ModId, Direction](const TOptional<FCFCoreApiResponseError>& Error) {
if (Error.IsSet()) {
HandleAuthorizedApiError(Error);
RefreshVoteButtons(); // roll the optimistic UI back
return;
}
MyUpvotedModIds.Remove(ModId);
MyDownvotedModIds.Remove(ModId);
(Direction == ECFCoreRatingVoteDirection::UpVote
? MyUpvotedModIds : MyDownvotedModIds).Add(ModId);
RefreshVoteButtons();
}));
}
Blueprint: Update Mod Rating, input pins modId (integer 64) and direction (an ECFCoreRatingVoteDirection dropdown), delegate pins on_success (no outputs) and on_error. Remove Mod Rating, input pin modId, same two delegate pins. Selecting None in the dropdown records an up vote.
Neither call returns an updated FRatingDetails. If a card shows the community score and the player's vote together, re-fetch the mod through ICFCoreApi::GetMods or ICFCoreApi::GetMod.
Reporting a mod
Reporting is your moderation intake. The SDK gives you two paths: an in-game report you submit, and a handoff to CurseForge's own web reporting pages. Both start from a list of reasons the backend owns. For what happens to a report after submission, see Moderation.
Fetching the reporting reasons
GetReportingReasons returns an array of FReportingReason valid for the report API.
Do not hardcode reasons or their ids. The list is server-owned and can change, and id is the only value ReportMod accepts. A stale id fails the call rather than degrading gracefully. Fetch the list when the report dialog opens, cache it for the session, and drive your picker from the response.
FReportingReason field | Type | Use |
|---|---|---|
id | int64 | The value to pass as reasonId to ReportMod. Defaults to 0. |
slug | FString | Machine-readable key, for localization lookups and analytics. Not guaranteed stable, so do not treat it as a permanent primary key. |
name | FString | Short label for the picker entry. |
description | FString | Longer explanation. Render as helper text under the selected reason. |
Blueprint: Get Reporting Reasons, pins on_results (output reasons, array of FReportingReason) and on_error. Store each id on your list entry widget.
Submitting the report
You do not construct the request body yourself, it is assembled from the parameters below.
ReportMod parameter | Type | Notes |
|---|---|---|
mod_id | int64 | The mod being reported. |
reasonId | int64 | An id taken from the GetReportingReasons response. |
report | FString | The player's free-text description. |
delegate | FReportModDelegate | Receives only an optional FCFCoreApiResponseError. |
How it works
- The player opens the report dialog and picks a reason from the cached list.
- The player types a description.
- Call
ReportModwith the mod id, the chosen reason id and the text. - An unset error means the report was accepted. Confirm to the player and close the dialog.
Close the dialog on success rather than leaving submit live. The SDK performs a plain POST with no idempotency handling and no local deduplication, so nothing on the client stops a second submission of the same report.
const int64 ReasonId = CachedReasons[SelectedIndex].id;
SubmitButton->SetIsEnabled(false);
Authorized->ReportMod(ModId, ReasonId, ReportText,
ICFCoreApiAuthorized::FReportModDelegate::CreateWeakLambda(this,
[this](const TOptional<FCFCoreApiResponseError>& Error) {
if (Error.IsSet()) {
HandleAuthorizedApiError(Error);
SubmitButton->SetIsEnabled(true);
return;
}
ShowReportAcceptedMessage();
CloseDialog();
}));
Blueprint: Report Mod, input pins modId (integer 64), reasonId (integer 64) and report (string). The success pin is named on_reasons, not on_success, and carries no outputs. The error pin is on_error.
Temporary tokens for the web reporting pages
Some report types are better handled by CurseForge's own web pages than by an in-game dialog, for example one that needs file uploads. GenerateTempToken exists for that handoff: it generates a temporary user token you can use to authenticate the player in external services, for example when reporting a mod through the CurseForge web-based reporting pages.
How it works
- Call
GenerateTempTokenwhen the player chooses the web reporting path. - The delegate returns the token as an
FString. It identifies the player to the external page, so the page does not ask them to sign in again. - Compose the reporting URL with the token attached as that page expects, then open it with
FPlatformProcess::LaunchURL(includeHAL/PlatformProcess.h) or in your in-game browser surface.
The token authenticates the player. Treat it as a credential: do not log it, do not persist it to disk, and do not put it where another process can read it back. The SDK documents no expiry and you cannot test a held token, which is why minting one per handoff is the safe default rather than caching one from startup.
Blueprint: Generate Temp User Token, pins on_result (output temp_user_token, type string) and on_error.
The SDK does not expose the reporting page URL, the query parameter name for the token, or the token lifetime. The shipped mod browser composes a URL of the shape https://report-ui.forgecdn.net/pages/<game-segment>/<mod-or-server>/entry?token=<temp-token> and opens it with LaunchURL, which confirms the host, path shape and query parameter name (token). The token's expiry window is still not documented anywhere. Confirm both open items for your title with cfforstudios@overwolf.com before shipping the handoff.
Blocking on modded servers
Blocking matters most in the modded-server case: a player joins a community server, gets a mod list pushed at them, and decides they never want that server again, or never want that mod on any server. GetBlockedDetails covers both granularities: a player can block a server and/or mods on a server, so that they either block the server or all servers that contain blocked mods. Blocking a server hides one server. Blocking a mod in the server context hides every server carrying that mod. For the client and server mod sync this sits inside, see multiplayer and servers.
Both lists are scoped to the current game. A block set in a different CurseForge title does not appear here.
cfcore-sdk-ue exposes reading and removing blocks, not creating them. There is no Block method on ICFCoreApiAuthorized, no block node on UCFCoreSubsystem, and no block-creation method anywhere else in the public SDK surface. FBlockedDetails::blockedUIModIds and FUnblockModsRequest::blockedMods are made from within the in-game browser or website, which is where blocks originate. Your game reads the resulting lists and honours them.
Reading what the player has blocked
FBlockedDetails carries four lists: two for the join-server screen, and two for blocks made through the in-game browser or website. Handle all four if your title surfaces mods outside the join flow. All four are BlueprintReadOnly.
FBlockedDetails field | Type | What it holds |
|---|---|---|
serverIds | TArray<FString> | Server ids the player has blocked. These are strings, not integers. |
modIds | TArray<int64> | Mod ids blocked in the context of modded servers. Any server carrying one of these is filtered. |
blockedUIModIds | TArray<int64> | Mods blocked by the player from within the in-game browser or website. |
blockedUIAuthorIds | TArray<int64> | Authors blocked by the player from within the in-game browser or website. |
How it works
- Call
GetBlockedDetailsbefore you render a server browser or mod list, and copy the four arrays into four lookup sets. - Filter your server browser against
serverIds. - Filter it again against
modIds: drop any server whose mod list intersects that array. - Filter your mod browser against
blockedUIModIds, and againstblockedUIAuthorIdsby comparing theidof each entry in the mod'sauthorsarray.FModAuthor::idisint64, so no cast is needed. - Re-call after a sign-in change or after the player returns from the in-game browser. Blocks can be created there and the SDK exposes no change notification.
Blueprint: Get Blocked Mods Details, pins on_result (output blocked_details, type FBlockedDetails) and on_error. The display name says Mods, but it returns servers as well.
Unblocking
UnblockMods takes an FUnblockModsRequest and unblocks mods and servers previously blocked by the current player for the current game. All four fields are BlueprintReadWrite, which is why a Blueprint author can build one.
FUnblockModsRequest field | Type | Meaning |
|---|---|---|
blockedAuthors | TArray<int64> | Authors blocked by the player from within the in-game browser or website. |
blockedMods | TArray<int64> | Mods blocked by the player from within the in-game browser or website. |
blockedServerMods | TArray<int64> | Mods blocked by the player from within the context of a server, for example a join server screen. |
blockedServers | TArray<FString> | Servers blocked by the player. |
The two structs map onto each other:
Read from FBlockedDetails | Send in FUnblockModsRequest |
|---|---|
serverIds | blockedServers |
modIds (server context) | blockedServerMods |
blockedUIModIds | blockedMods |
blockedUIAuthorIds | blockedAuthors |
blockedMods and blockedServerMods are distinct fields, not synonyms. Sending a server-context mod id in blockedMods targets the wrong list. Keep the two mod arrays separate from the point you read them to the point you send them.
Fill only the arrays you have entries for, send one request rather than looping per id, then re-call GetBlockedDetails and re-apply your filters.
FUnblockModsRequest Request;
Request.blockedServers = ServersToRestore; // FBlockedDetails::serverIds
Request.blockedServerMods = ServerModsToRestore; // FBlockedDetails::modIds
Request.blockedMods = BrowserModsToRestore; // blockedUIModIds
Request.blockedAuthors = AuthorsToRestore; // blockedUIAuthorIds
Authorized->UnblockMods(Request,
ICFCoreApiAuthorized::FUnblockModsDelegate::CreateWeakLambda(this,
[this](const TOptional<FCFCoreApiResponseError>& Error) {
if (!Error.IsSet()) {
FetchBlockedDetails(); // re-read the authoritative lists
}
}));
Blueprint: Unblock Mods, input pin Request of type FUnblockModsRequest (use a make-struct node, or set the four arrays on a struct variable). Note the capitalized delegate pins on this node: OnResult (output success, boolean) and OnError. success is true whenever no error came back, so it is a confirmation flag, not a per-id result.
Errors on these calls
Every method here fails the same way, so one handler covers all of them. In C++ the payload is FCFCoreApiResponseError; in Blueprint the error pin carries FCFCoreError, whose apiError member is that same struct. The flags that matter most on authorized calls are tokenExpired (401), missingPrivileges (403, signed in but not permitted), entityNotFound (404, where a stale reasonId lands), badRequest (400), serverUnreachable (transport failure or 5xx, safe to retry) and cancelled (do not surface a message). The full flag list, the HTTP mapping and the errorCode reference are on Error handling.
A tokenExpired response has a side effect. The SDK erases the locally stored auth token when it sees one, so IsAuthenticated() and the Is Authenticated node start returning false immediately afterwards. A retry on the same session cannot succeed. Send the player back through authentication first, then retry.
If a call fails with an errorCode you cannot map to a player-facing message, contact cfforstudios@overwolf.com with the code, the description and the method name, or check your project configuration in the Developer Portal at console.curseforge.com.