Analytics
Analytics: data collection is the field-level reference, alongside overview and dashboards. It is already Unreal-specific, so this page does not repeat it. It adds the plumbing: where the setting lives in your project, where each call belongs in the Unreal lifecycle, and how to wire the funnel calls from UMG. Facts here are from cfcore-sdk-ue.
The two categories
| Category | Setting field | Default | Who fires the events |
|---|---|---|---|
| Performance and Stability | performanceAndStability | true | The SDK, automatically, on every mod install and update |
| User Engagement | userEngagement | false | Mostly your game, by calling the three methods below |
Both are fields on FCFCoreSettingsAnalytics, exposed as the analytics member of FCFCoreSettings.
Turning on User Engagement does not make cfcore itself collect funnel or session data. Nothing inside cfcore calls the three public analytics methods. The bundled cfcore-sdk-ue-ui mod browser calls SendModBrowsingFunnelImpression and SendModBrowsingFunnelAction itself as part of normal browsing, so shipping it as-is already populates the browsing funnel. SendGamePlaySession is not part of that: no bundled UI calls it, so session data still comes only from a call your game makes.
One User Engagement event is fired by the SDK and not by you. The premium mods language test action starts flowing as soon as the category is on. Account for it in your consent copy.
Where the setting lives in your project
| How you build settings | What to do |
|---|---|
| Project Settings (recommended) | Open Edit > Project Settings > Plugins > CFCore and set the two booleans under the analytics group. They are config properties on UCFCoreEditorSettings, persisted to DefaultGame.ini under [/Script/cfcore.CFCoreEditorSettings] |
| Blueprint, Make Settings From Project Config | Nothing extra. The node copies the project config, including analytics, into the returned struct |
| Blueprint, Make Settings | Set the analytics member on the returned struct yourself. Make Settings takes six arguments and none of them is analytics |
| C++ | Set settings.analytics.performanceAndStability and settings.analytics.userEngagement before you call Initialize |
If you build settings with Make Settings and never touch analytics, you ship the struct defaults regardless of what the Project Settings checkboxes say.
There is no runtime toggle. FCFCoreUpdatableSettings, the struct Update Settings accepts, carries only updateDefaultLanguage and defaultLanguage. To change a category after startup you must call Uninitialize and initialize again with different settings. Plan your consent UI around that.
Initialize before you call
ICFCore::Analytics() returns nullptr until initialization completes, so in C++ an early call is a null dereference and a crash, not an error. The Blueprint nodes check first and fire OnError with ECFCoreErrorCodes::FailedToInitialize and the description "Not initialized". There is no offline buffer and no retry, so an event sent too early is lost. Gate every call site on IsInitialized() and null-check Analytics().
Collection can also be limited server side. FMePhasingData carries the read-only analyticsUserEngagement and analyticsPerfAndStability, both int32 from 0 to 100 and both defaulting to 100, where 0 disables the category for everyone and any other value is a per-user rollout percentage. Read them off FMe::phasingData with the Get Me node (ApiGetMe) before you escalate thin reports.
The three methods you call
C++ method (ICFCoreAnalytics) | Blueprint node | Params | When to call |
|---|---|---|---|
SendGamePlaySession | Send Game Play Session Analytic | FGamePlaySessionParams | Once, after a play session ends |
SendModBrowsingFunnelImpression | Send Mod Browsing Funnel Impression Analytic | FModBrowsingFunnelParams | When browser content becomes visible to the player |
SendModBrowsingFunnelAction | Send Mod Browsing Funnel Action Analytic | FModBrowsingFunnelParams | When the player installs, purchases, uninstalls, or updates from that content |
All three sit on UCFCoreSubsystem in the palette category cfcore|Analytics, each with Target, InParams, OnSuccess and OnError pins. The C++ versions return bool immediately.
Where the session call belongs
There is no start, heartbeat, or end lifecycle here. One call reports the whole session, after it is over.
- Record the wall-clock start when the session begins, in
UGameInstance::Initor yourAGameModeBase::StartPlayoverride. AUGameInstanceSubsystemis the cleanest holder because it outlives level transitions. - Compute the length in whole seconds and send from wherever the session actually ends:
AGameModeBase::EndPlay, your return-to-menu handler, orUGameInstance::Shutdown. Never fromTick. - Collect the mod IDs that were loaded for that session.
- Set
sessionTypetoLocalorServer, and setserverNamefor a server session. - Send once, for every session, modded and unmodded, so the two are comparable.
#include <cfcore_context.h>
#include <analytics/cfcore_analytics.h>
#include <analytics/models/game_play_session_params.h>
void AMyGameMode::ReportSessionEnded(int32 SessionSeconds,
const TArray<int64>& LoadedModIds) {
cfcore::ICFCore* CFCore = cfcore::CFCoreContext::GetInstance();
if (!CFCore || !CFCore->IsInitialized() || !CFCore->Analytics()) {
return; // Nothing to queue into. The event is dropped.
}
FGamePlaySessionParams Params;
Params.sessionLengthInSecs = SessionSeconds;
Params.sessionType = ECFCoreSessionType::Local;
Params.modIds = LoadedModIds; // Empty array is valid and is transmitted.
if (!CFCore->Analytics()->SendGamePlaySession(Params)) {
// Payload serialization failed. Check the cfcore log.
}
}
Blueprint: feed Send Game Play Session Analytic from the Make node for FGamePlaySessionParams (sessionLengthInSecs, sessionType, modIds, serverName) and bind OnSuccess and OnError to Custom Events.
Session payloads are not stripped of empty fields, unlike funnel payloads. An empty serverName and an empty modIds array are both sent, which is the correct way to record an unmodded local session. Ignore the stale doc comment on sessionLengthInSecs reading "When was the mod file updated/installed": the field is the session length in seconds.
Wiring funnel calls from UMG
Impressions record what the player saw, actions record what they did about it. Both use FModBrowsingFunnelParams, so the same location fields describe both halves and the pair joins into a conversion rate for a page, a shelf, or a carousel slot.
- Fix your naming scheme for
pageName,pageComponent,shelfName, andactionTypefirst. All four are free-form strings that the SDK neither validates nor normalises, and reports group on the literal string. - Send the impression when the surface becomes visible.
NativeConstructfires only on construction, so for a reused widget hook visibility instead:UUserWidget::OnVisibilityChanged, an Activated event on aUCommonActivatableWidget, or your own entry-shown callback when the shelf is aUListViewrecycling entry widgets. One impression per entry, not one per page. - Send the action from the same widget's input handler, for example the install button's
OnClickeddelegate, with the same location fields plusactionType. - Leave fields you do not have alone. Empty strings are stripped from the payload, and numeric fields left at
CF_INVALID_NUMBER(defined as-1incommon/defines.h) are skipped.modId,pageId, anditemPositionall default to it, so a Make node needs no placeholders.
// Same includes as above, with mod_browsing_funnel_params.h in place of
// game_play_session_params.h.
void UMyModShelfEntry::ReportFunnel(bool bIsAction) {
cfcore::ICFCore* CFCore = cfcore::CFCoreContext::GetInstance();
if (!CFCore || !CFCore->IsInitialized() || !CFCore->Analytics()) {
return; // Same guard as above. Analytics() is nullptr before init.
}
FModBrowsingFunnelParams Params;
Params.pageName = TEXT("browse");
Params.pageComponent = TEXT("featured_carousel");
Params.shelfName = ShelfName;
Params.modId = ModId;
Params.itemPosition = EntryIndex; // pageId stays at CF_INVALID_NUMBER.
if (bIsAction) {
Params.actionType = TEXT("install");
CFCore->Analytics()->SendModBrowsingFunnelAction(Params);
} else {
CFCore->Analytics()->SendModBrowsingFunnelImpression(Params);
}
}
Blueprint: call Send Mod Browsing Funnel Impression Analytic from the widget's visibility or activation event and Send Mod Browsing Funnel Action Analytic from its button OnClicked event, both with InParams from the same Make node for FModBrowsingFunnelParams.
What success does not tell you
| Situation | C++ result | Blueprint result |
|---|---|---|
| Category disabled locally, or held back by the phasing flag | true | OnSuccess fires |
| Payload serialization failed | false | OnError, ECFCoreErrorCodes::ApiError, empty description. The log line is "Failed to serialize analytics payload" |
| SDK not initialized | crash on a nullptr Analytics() | OnError, ECFCoreErrorCodes::FailedToInitialize |
A rejected category is not an error: the send returns true and OnSuccess fires even though nothing was transmitted. The send is a fire-and-forget HTTP request whose response is ignored, so there is no delivery confirmation either. To verify an integration end to end, confirm the setting in Edit > Project Settings > Plugins > CFCore, confirm initialization succeeded, read the phasing values from Get Me, then check that rows appear in your reports in the Developer Portal at console.curseforge.com.
One field on the automatic install event is under your control: origin, from FInstallModAdditionalParams::origin on each install call. Leave it at its default and every install is attributed to a mod page, including server joins and background subscription installs. See Mod installation methods.
Escalation
For phasing flags that look like they are suppressing a category, for missing data in your reports, or to confirm which report row an event feeds, contact cfforstudios@overwolf.com with your game ID, the plugin version, the two phasing values from Get Me, and the category and event in question.