Unreal integration overview
CurseForge for Studios ships as a native Unreal Engine plugin suite, so supporting mods does not mean rebuilding how your team works. Your engineers get an integration path they already recognise: Blueprint nodes for design-side iteration, and a C++ interface for the systems programmers who need direct control. Both reach the same functionality, so the choice is a matter of team preference rather than a limit on what you can build.
The plugin suite covers the whole player-facing mod experience. Players can sign in, find mods, install and update them, keep a library in sync across their devices, rate and report what they find, and buy premium mods, all without leaving your game. CurseForge operates the hosting, the moderation pipeline, the payment providers, and the cross-platform build service behind all of that, so the work left on your side is narrower than studios usually expect: decide where mods live on disk, decide when your game loads them, and decide which optional pieces of the suite you want.
Three modules ship separately so you can adopt as much or as little as fits your plan. Taking only the core module gives you mod installation and loading with a UI you build yourself. Adding the UI module gives you a white-label in-game mod browser that is already built and already console-compliant. Adding the editor module lets mod authors publish from inside your own creation kit.
Being native Unreal is the point, and it has practical consequences for your team. This is not a wrapper around a foreign SDK. It is Unreal code, using Unreal types and Unreal delegates, compiled by your toolchain and shipped inside your binary.
- The full source ships with it under an MIT license. Your engineers can read it, step through it in their own debugger, patch it, and fork it. Nothing is a black box, and no fix is gated on our release schedule.
- There is no interop layer between your game and the SDK, so there is no marshalling cost and no second memory model to reason about.
- There is nothing to pump. Asynchronous work returns on the game thread on its own, with no handler loop for your team to tick every frame or keep alive on a background thread.
- Blueprint support is first class rather than generated bindings over a foreign API, so design-side staff can build and iterate without an engineer in the loop.
- Because it builds as part of your project, it moves with your engine upgrades and your CI instead of waiting on a prebuilt binary that matches your engine version.
If you want help scoping that project against your release schedule, contact cfforstudios@overwolf.com.
SDK modules
| Module | Repository | What it does |
|---|---|---|
| cfcore-sdk-ue | cfcore-sdk-ue | The core SDK. Wraps the CurseForge API so your game can browse, install, update, and manage mods. Required. |
| cfcore-sdk-ue-ui | cfcore-sdk-ue-ui | A white-label in-game mod browser covering discovery, browsing, search, the mod page, ratings, sign-in, purchasing, subscriptions, and server-mod reconciliation across platforms. Optional. |
| cf-ue-editor-plugin | cf-ue-editor-plugin | An Unreal Editor plugin so mod authors can upload to CurseForge from inside your creation kit. Optional. |
All three are open source under the MIT license. These pages document the current release of cfcore-sdk-ue. Each plugin carries its own version, readable from the VersionName field of its .uplugin descriptor, and all three are versioned independently.
Engine compatibility
The Unreal solution supports UE4.27, UE5.2, UE5.4, UE5.5, UE5.6, and UE5.8.
Supported engine versions change as new releases ship, so treat this list as current rather than permanent. If your game needs a build for an engine version not listed, contact cfforstudios@overwolf.com.
Platform compatibility
Windows, PlayStation 4, PlayStation 5, Xbox, Windows (GDK), and Nintendo Switch.
The ECFCorePlatform enum in the SDK is not this list. It carries members the SDK uses internally for cooking and file matching, including targets that are not commercially supported, and it does not carry a member for every supported target. Treat the list above as authoritative for what CurseForge supports, and confirm your exact target with cfforstudios@overwolf.com before planning a port. The enum itself is documented on Game info and platforms.
Console platforms carry first-party requirements that affect moderation and content review. See Xbox and PlayStation compliance and Platforms support before planning a console launch.
Plugin structure
The SDK is three separate Unreal plugins. Only the first is required. The module names above are the distribution names; the plugin folders inside the downloaded package are named as below, and those are the names you use when extracting and when adding a build dependency.
| Plugin folder | Required | What it is |
|---|---|---|
cfcore | Yes | The business logic plugin: search, install, update and manage mods locally, with no user interface. |
cfcore_ui | No | A UMG in-game mod browser built on top of cfcore. See Mod browser. |
cfeditor | No | An Unreal Editor plugin for integrating a game editor with CurseForge. |
cfcore contains exactly one module, also called cfcore, of type Runtime with loading phase Default, entry point FCFCoreModule.
The settings page under Project Settings > Plugins > CFCore is registered inside #if WITH_EDITOR, so it does not exist in a packaged build. The values still ship, because they live on UCFCoreEditorSettings, a config = Game, defaultconfig object read back at runtime by UCFCoreBPLibrary::MakeSettingsFromProjectConfig. Module unload also uninitializes the SDK for you, but do not use that as your shutdown path: unload happens late and gives you no callback to sequence against.
To use the C++ interface, add "cfcore" to PublicDependencyModuleNames in your game's Build.cs. The Blueprint interface needs no build change. See Installation.
The two development interfaces
The SDK exposes the same functionality twice. There is one engine underneath: the Blueprint subsystem is a thin forwarding layer over the C++ context, so the two can be mixed in one project, for example initialize in C++ and drive a browser UI in Blueprint.
| Blueprint | C++ | |
|---|---|---|
| Entry point | UCFCoreSubsystem, a UEngineSubsystem | cfcore::CFCoreContext::GetInstance(), returning ICFCore* |
| Lifetime | Engine-managed for the whole engine session, not per world | Function-local static created on first access. CFCoreContext is non-copyable |
| Shape | One flat class: every operation is a UFUNCTION on the subsystem | Sectioned: ICFCore hands out one interface per area |
| Callback style | Separate dynamic delegate pins per outcome | One delegate carrying an optional payload and an optional error |
| Error type | Always FCFCoreError | FCFCoreError or FCFCoreApiResponseError, depending on the area |
| Initialization check | None exposed. You discover it through the error | ICFCore::IsInitialized() |
ICFCore has eight sub-interface accessors, plus IsInitialized() and UpdateSettings(). The eight are mapped to their Blueprint categories under Blueprint node categories below. Three of them nest further. The nested accessors and what each returns are below.
| Parent | Accessor | Returns |
|---|---|---|
ICFCoreApi | Authentication() | TSharedPtr<ICFCoreApiAuthentication> |
ICFCoreApi | Authorized() | TSharedPtr<ICFCoreApiAuthorized> |
ICFCoreApi | Creation() | TSharedPtr<ICFCoreApiCreation> |
ICFCoreLibrary | ClientServerLibrary() | ICFCoreClientServerLibrary* |
ICFCoreUtils | Compression() | ICompressionService* |
How the two bindings report errors
This is the one structural difference that changes how you write code, and it is the difference most often got wrong.
| C++ | Blueprint | |
|---|---|---|
| Shape | One delegate. It carries TOptional<FCFCoreError>, or TOptional<FCFCoreApiResponseError> on API calls, alongside the optional payload. | Two or more delegate pins. Success and error are separate pins on the same node. |
| Test | Check whether the error optional IsSet(). Only if it is not, read the payload with GetValue(). | Bind each pin to its own Custom Event. The engine picks the branch for you. |
| Example | SendSecurityCode(email, FSendSecurityCodeDelegate), where the delegate takes const TOptional<FCFCoreError>& | Send Security Code Email, with on_success and on_error pins |
Sub-interface accessors return null before initialization
Read this before you write your first call. Every ICFCore sub-interface accessor returns nullptr until initialization has succeeded. Api(), Library(), Authentication(), Creation(), PremiumMods(), Analytics() and Subscription() all test an internal initialized flag first and return nullptr when it is false. Dereferencing one before initialization completes is a crash, not an error delivered to a delegate, so no error pin or error optional will tell you about it.
Utils() is the single exception. It has no initialization check and is safe to call at any time, which is why the compression helper works before the SDK is up.
Null-check the accessor rather than trusting a prior IsInitialized() call, because initialization is asynchronous and can complete or fail between your check and your call:
ICFCoreLibrary* library = CFCoreContext::GetInstance()->Library();
if (!library) {
// Not initialized yet. Do not proceed.
return;
}
library->GetInstalledMods(/* ... */);
The Blueprint nodes protect you here: each one guards internally and fires its error pin with FailedToInitialize instead of crashing. This rule matters for C++ callers. See Error handling and logging.
How it works
- Get the interface for the area you need, and null-check it.
- Construct the delegate declared inside that interface, with
CreateUObject,CreateWeakLambdaorCreateLambda. - Call the function. It returns
voidimmediately. - In the callback, check the error first. Only then read the payload.
#include <cfcore_context.h>
using namespace cfcore;
CFCoreContext::GetInstance()->Library()->GetModsDirInfo(
ICFCoreLibrary::FGetModsDirInfoDelegate::CreateWeakLambda(
this,
[](const TOptional<FModsDirInfo>& opt_info,
const TOptional<FCFCoreError>& opt_err) {
if (opt_err.IsSet()) {
// Branch on opt_err.GetValue().code
return;
}
if (!opt_info.IsSet()) {
return;
}
const FModsDirInfo& info = opt_info.GetValue();
}));
Blueprint: the same call is the Get Mods Directory Info node (category cfcore|Library) with pins OnModsDirInfo and OnError. Bind each to its own Custom Event. Every delegate is invoked with ExecuteIfBound, so an unconnected pin discards the result in silence. Wire the error pin on every node.
Blueprint also flattens the two error types into one: on an API failure the subsystem sets code to ApiError and copies the whole API error into the apiError member. Both structs field by field, the complete ECFCoreErrorCodes table and the API error code table are on Error handling.
Blueprint node categories and C++ accessors
Nodes are organised under eleven palette category paths. This is how you find a node, and how each C++ area maps to a Blueprint area. The eight ICFCore accessors return ICFCoreApi*, ICFCoreLibrary*, ICFCoreAuthentication*, ICFCoreCreation*, ICFCorePremiumMods*, ICFCoreAnalytics*, ICFCoreSubscription* and ICFCoreUtils*.
| Blueprint category | C++ accessor | What it covers |
|---|---|---|
cfcore | ICFCore itself | Initialize, Uninitialize, Update Settings |
cfcore|Api | Api() | Unauthenticated catalogue queries: game info, versions, categories, search, highlights, mods, files, changelogs |
cfcore|Api Authorized | Api()->Authorized() | Calls needing an authenticated player: profile, ratings, reporting, subscriptions, purchases, entitlements, blocked mods |
cfcore|Subscription | Subscription() | Automatic subscription management, sync plans, subscribe and install |
cfcore|Library | Library() | The local library: installed mods, directories, install, uninstall, validation, properties, load order |
cfcore|Library|ClientServer | Library()->ClientServerLibrary() | Server and client mod set reconciliation |
cfcore|Authentication | Authentication() | Session state, terms, email OTP, external providers, logout |
cfcore|Creation | Creation() | Creating and updating mods, uploading mod files, cooked files |
cfcore|Premium Mods | PremiumMods() | Ownership checks, purchase polling, key overrides, file details |
cfcore|Analytics | Analytics() | Play session and mod browsing funnel events |
cfcore|Utils|Compression | Utils()->Compression() | Zipping paths, with progress |
Two Blueprint nodes are named Subscribe and two are named Unsubscribe, separated only by category. The cfcore|Subscription versions also install and uninstall the mod. The cfcore|Api Authorized versions only change the server-side subscription. Picking the wrong one is a silent bug. See Subscriptions.
Threading and callbacks
| Guarantee | Detail |
|---|---|
| Everything is asynchronous | Nothing in the public interface blocks on network or disk. |
| Callbacks arrive on the game thread | Every async operation calls back on the game's main thread. The off-thread services (filesystem, compression, hashing, binary diff) marshal results back through AsyncTask(ENamedThreads::GameThread, ...). |
| You call in on the game thread | The SDK assumes every call into it comes from the game's main thread, and uses that to avoid locks. Calling from a worker thread is out of contract. |
| Heavy work is off-thread | Compression and disk IO run on their own threads, so the game loop is not blocked. |
| There is no event loop to pump | Neither binding exposes a tick or a "run pending handlers" function. Nothing needs calling every frame for callbacks to fire. |
| Payloads are Unreal value types | USTRUCT types delivered by value or const reference. Nothing to release, no ownership transfer out. |
Initialization and teardown
Initialization is where the SDK learns your game identity and where its two directories live. It is asynchronous, so a fire and forget call teaches you nothing: success is only known in the callback.
A game set up on the Developer Portal at console.curseforge.com, which gives you the game id and the game API key. See Setting up your game.
How it works
- Build the settings struct, by hand or from Project Settings.
- Set the per-player context id, so the SDK knows which local user file to use.
- Call initialize and wait for the callback. Five settings are validated synchronously first, before any network request, so a configuration mistake fails immediately:
modsDirectory,modsDirectoryMode,userDataDirectory,gameId, andapiKey. - On success every other call becomes legal. On failure the SDK stays uninitialized.
- On shutdown, stop issuing calls, disable automatic subscription management if it is on, then call uninitialize and wait for its callback.
Almost every guarded call made before initialization completes fails identically: it never reaches the SDK, and the error arrives with code set to FailedToInitialize and description set to the literal string Not initialized. The settings reference, validation order, directory escape tokens and the nodes that behave differently are on Initialization and settings.
If you mix the two bindings, initialize through one of them only. On success, the Blueprint Initialize node calls an internal registration step that first calls Clear() on both ICFCoreLibrary multicast delegates, OnModInstallProgress and OnModInstalled, and then binds its own forwarders so it can rebroadcast through the subsystem's OnModInstallProgress and OnModInstalled assignable properties. Any C++ handler you bound to those two library delegates beforehand is silently discarded. There is no warning and no error. If your project initializes in Blueprint and listens for install events in C++, bind your C++ handlers after the Blueprint initialize callback has fired, or listen through the subsystem's assignable delegates instead of the library's.
The in-game mod browser plugin
cfcore_ui is a client of cfcore, not a replacement for it. Anything the shipped browser does, your own UI can do by calling cfcore directly, so the decision is about who owns the widgets, not about what is reachable.
The plugin declares one module, cfcore_ui, of type Runtime, with a hard dependency on cfcore. The browser therefore exists in a packaged shipping build and there is no separate editor module to strip. It ships UMG assets in its Content folder, an input layer with PlayStation, Xbox, and keyboard-and-mouse glyph sets, and a theming data table. It also ships a substantially larger surface than "browse and install": a sign-in and email OTP flow, a terms-and-conditions prompt, ratings, a store menu with price columns and code redemption, subscription-diff widgets, and server-mod reconciliation widgets for the client-join case.
The entry point is UCFCoreUISubsystem, a world subsystem, not a game instance subsystem. Its state, cached images, ratings, purchased-mods list, trial status, the registered model, and view subscriptions, does not survive a level transition, and it is fetched per world with GetWorld()->GetSubsystem<UCFCoreUISubsystem>(), not through the engine. This is a different lifetime than the core cfcore subsystem, which is engine-scoped. Bring-up is: call InitializeUIController once for that world, register a model class before any action, then subscribe your widgets.
Purchases are two delegate properties you assign, UIPurchaseModDelegate (one mod id) and UIPurchaseModsDelegate (an array of mod ids), because the plugin does not know how your game takes money. Load order is computed in the UI plugin and persisted by cfcore through Update Installed Mods Properties.
The shipped Discover screen calls the mod highlights API, which must be enabled for your game in the Developer Portal at console.curseforge.com before it returns data. Contact cfforstudios@overwolf.com to have it enabled.
Screens, setup steps, theming and screenshots are on Mod browser, and are not repeated here.
Guides in this section
Read in this order. For a summary of what every page contains, see In this section.
- Getting started. The whole integration in order, from adding mod support to loading an installed mod, with a link to the page that covers each step in full.
- Installation. Extracting the plugins, rebuilding, enabling them, the
Build.cschange. - Initialization and settings. The settings reference, directory escape tokens, validation order, what can change after startup.
- Error handling.
FCFCoreError,FCFCoreApiResponseError, the complete error code tables, the on-disk file logger. - Authentication. Email OTP, external providers, terms and consent, logout. Concepts and the provider matrix: Authentication.
- Browsing and discovery. Search filters, pagination, highlights, the mod and file models. Carousels and shelves are configured with the content curation tools.
- Installing and managing mods. The local library, install parameters, statuses, validation, updates, load order. Taxonomy: mod installation methods.
- Mod dependencies. Relation types, resolving a dependency graph yourself, hashes, modules and fingerprints, per-platform file matching.
- Subscriptions. Automatic management, sync plans, the two
Subscribenodes. - Multiplayer and servers. Client and server mod reconciliation, platform file matching, dedicated server settings.
- Monetization. Premium mods, purchase flows and polling, trials and freemium, entitlements, signature checking.
- Creating and uploading mods. Mod creation and file upload from your game or creation kit. The build service: cloud cooking.
- Player actions. Ratings, reporting, blocked mods and blocked servers. Review layers: moderation.
- Analytics. Which events the SDK sends and which you send. Categories and dashboards: analytics.
- Game info and platforms. The game record, supported-feature flags, the version taxonomy, the three platform vocabularies, the compression service.
- Mod browser. The shipped in-game UI.
Before players can see mods, your game record has to be tested and approved: see Testing and launching your game.