Skip to main content

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

CategorySetting fieldDefaultWho fires the events
Performance and StabilityperformanceAndStabilitytrueThe SDK, automatically, on every mod install and update
User EngagementuserEngagementfalseMostly your game, by calling the three methods below

Both are fields on FCFCoreSettingsAnalytics, exposed as the analytics member of FCFCoreSettings.

warning

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.

note

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 settingsWhat 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 ConfigNothing extra. The node copies the project config, including analytics, into the returned struct
Blueprint, Make SettingsSet 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
warning

If you build settings with Make Settings and never touch analytics, you ship the struct defaults regardless of what the Project Settings checkboxes say.

warning

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 nodeParamsWhen to call
SendGamePlaySessionSend Game Play Session AnalyticFGamePlaySessionParamsOnce, after a play session ends
SendModBrowsingFunnelImpressionSend Mod Browsing Funnel Impression AnalyticFModBrowsingFunnelParamsWhen browser content becomes visible to the player
SendModBrowsingFunnelActionSend Mod Browsing Funnel Action AnalyticFModBrowsingFunnelParamsWhen 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.

  1. Record the wall-clock start when the session begins, in UGameInstance::Init or your AGameModeBase::StartPlay override. A UGameInstanceSubsystem is the cleanest holder because it outlives level transitions.
  2. Compute the length in whole seconds and send from wherever the session actually ends: AGameModeBase::EndPlay, your return-to-menu handler, or UGameInstance::Shutdown. Never from Tick.
  3. Collect the mod IDs that were loaded for that session.
  4. Set sessionType to Local or Server, and set serverName for a server session.
  5. 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.

note

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.

  1. Fix your naming scheme for pageName, pageComponent, shelfName, and actionType first. All four are free-form strings that the SDK neither validates nor normalises, and reports group on the literal string.
  2. Send the impression when the surface becomes visible. NativeConstruct fires only on construction, so for a reused widget hook visibility instead: UUserWidget::OnVisibilityChanged, an Activated event on a UCommonActivatableWidget, or your own entry-shown callback when the shelf is a UListView recycling entry widgets. One impression per entry, not one per page.
  3. Send the action from the same widget's input handler, for example the install button's OnClicked delegate, with the same location fields plus actionType.
  4. Leave fields you do not have alone. Empty strings are stripped from the payload, and numeric fields left at CF_INVALID_NUMBER (defined as -1 in common/defines.h) are skipped. modId, pageId, and itemPosition all 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

SituationC++ resultBlueprint result
Category disabled locally, or held back by the phasing flagtrueOnSuccess fires
Payload serialization failedfalseOnError, ECFCoreErrorCodes::ApiError, empty description. The log line is "Failed to serialize analytics payload"
SDK not initializedcrash 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.