Skip to main content

Creating and uploading UGC

The Creation section of the cfcore plugin in cfcore-sdk-ue: creating a mod record, uploading a mod file against it, chunking large files, splitting source from cooked uploads, and reading back what the server accepted.

What this page adds. The cloud cooking pages already document the pipeline and the upload flows with Unreal C++ samples: overview, how it works, upload flows, references. This page adds what those omit: every request-struct field, the avatar rule, the markup enums, the chunked transport, the progress and uploaded-file models, packaging, hashing, and the exact Blueprint nodes and pins.

info

The SDK is initialized and the player is authenticated. Game id and API key come from the Developer Portal at console.curseforge.com (see setting up your game). Every Creation call that reaches the network checks authentication first and completes with ECFCoreErrorCodes::UserNotAuthenticated (description user is not authenticated or token is invalid) before sending anything. Every Blueprint Creation node adds a second guard, IsInitialized, firing OnError with ECFCoreErrorCodes::FailedToInitialize (description Not initialized). The C++ interfaces do not, so from C++ call Creation only after initialization completes.

Which upload path to use

PathWho builds itWhat it covers
cf-ue-editor-pluginCurseForgeAn Unreal Editor plugin for your mod authors. See below
cfcore Creation, BlueprintYouFour nodes on UCFCoreSubsystem, listed below
cfcore Creation, C++YouICFCoreCreation, including the in-memory buffer overloads Blueprint does not expose
cfcore API layer, C++YouICFCoreApiCreation, including chunk-by-chunk control

The three cfcore rows are layers of one feature: UCFCoreSubsystem forwards to ICFCoreCreation, which forwards to ICFCoreApiCreation. Pick the highest layer that exposes what you need.

The editor plugin. Shipped separately at https://github.com/curseforge-sdks/cf-ue-editor-plugin. Your mod authors use it to publish from inside your creation kit. It adds a UGC menu with Sign In, Sign Out and Share UGC, then packages and uploads the mod the author selects. By default it uploads the mod source and lets CurseForge cook it, and authors can cook locally instead.

warning

The two C++ layers report errors differently. ICFCoreCreation completes with TOptional<FCFCoreError>, carrying an ECFCoreErrorCodes value plus a nested apiError. ICFCoreApiCreation completes with TOptional<FCFCoreApiResponseError> and no ECFCoreErrorCodes at all, so there you branch on the boolean flags and on errorCode. Do not reuse one handler across both layers.

The creation surface at a glance

OperationBlueprint nodeICFCoreCreationICFCoreApiCreation
Create a mod recordCreate New ModCreateMod, path or bufferCreateMod, buffer
Update a mod recordUpdate Existing ModUpdateMod, path or bufferUpdateMod, buffer
Upload a mod fileCreate Mod FileCreateModFile, path or bufferCreateModFile, buffer
Start a chunked mod filenot exposedreached automaticallyCreateModFileChunked
Upload one chunknot exposedreached automaticallyUploadModFileChunk
Upload a cooked fileCreate Cooked Mod FileCreateCookedModFile, path or bufferCreateCookedModFile, buffer
Start a chunked cooked filenot exposedreached automaticallyCreateCookedModFileChunked
Cancel an uploadnot exposedCancelCreationCancelCreation
warning

ICFCoreCreation::CancelCreation does not currently cancel anything. Calling it neither aborts the in-flight upload nor invokes its own delegate. Uploads started through the file-path overloads of CreateModFile and CreateCookedModFile go through this layer, so they cannot be cancelled from game code today, only by SDK shutdown. Uploads driven manually through ICFCoreApiCreation can still be cancelled, using the id returned by UploadModFileChunk with ICFCoreApiCreation::CancelCreation, as shown in chunked upload for large files below.

Blueprint nodes and pins

All four nodes sit in the palette category cfcore|Creation, alongside the BlueprintPure make nodes on UCFCoreBPLibrary that build their request structs.

NodePins
Create New Modcreate_mod_request, avatar_image_filename, on_success (payload mod), on_error (payload error)
Update Existing Modmod_id, update_mod_request, avatar_image_filename, on_success (payload mod), on_error
Create Mod FileModId, CreateModFileRequest, LocalFilenameToUpload, OnCreateModFileRequestId (payload mod_file_request_id), OnProgress (payload progress), OnSuccess (payload uploaded_file), OnError
Create Cooked Mod FileModId, SourceFileId, CreateCookedModFileRequest, LocalFilenameToUpload, OnCreateModFileRequestId, OnProgress, OnSuccess, OnError
MakeCreateModRequestclass_id, name, summary, description_type, description, primary_category_id, game_category_ids, is_experimental
MakeUpdateModRequestIdentical to MakeCreateModRequest
MakeCreateModFileRequestchangelog_type, changelog, filename, displayName, gameVersionIds, releaseType, cookingOptions, isMarkedForManualRelease. No fileLength pin, which is harmless because the node uses the file-path overload
MakeCreateCookedModFileRequestDoes not exist. Populate the struct's two members with a Make Struct node

Blueprint gets the file-path form only. The in-memory TSharedPtr<TArray<uint8>> overloads of CreateMod, UpdateMod, CreateModFile and CreateCookedModFile are C++ only, and there are no chunked upload or cancel nodes at all.

Delegates, by layer. On ICFCoreCreation the error parameter is const TOptional<FCFCoreError>&; on ICFCoreApiCreation it is const TOptional<FCFCoreApiResponseError>&. Every result parameter below is passed by const reference except int64.

DelegateResult parameterLayersBlueprint pin
FCreateModDelegateTOptional<FCFCoreMod>bothmod
FUpdateModDelegateTOptional<FCFCoreMod>bothmod
FCreateModFileDelegateTOptional<FUploadedModFile>bothuploaded_file
FCreateModFileChunkedDelegateTOptional<FModFileChunkedInfo>API onlynot exposed
FUploadModFileChunkDelegateboolAPI onlynot exposed
FCreateModFileRequestIdDelegateint64, no errorCreation onlymod_file_request_id
FFileTransferProgressDelegateFFileTransferProgress, no errorbothprogress
FCancelCreationDelegateerror onlyCreation onlynot exposed
FCFCoreErrorDelegateBlueprint onlynot applicableerror
note

The mod pin on Create New Mod and Update Existing Mod fires through FCFCoreCreateModDelegate and FCFCoreUpdateModDelegate respectively. The progress pin fires through FCreateModFileProgressDelegate.

Creating a mod record

A mod record is metadata only. It has no downloadable content until you attach a mod file.

How it works

  1. Resolve the classId: call ICFCoreApi::GetCategories with FCFCoreGetCategoriesFilter::classesOnly true. A returned FCategory with isClass true is a class, and its id is your classId.
  2. Resolve primaryCategoryId and gameCategoryIds from the same call, filtering by FCFCoreGetCategoriesFilter::classId.
  3. Fill an FCreateModRequest.
  4. Point the call at an avatar image on disk, or hand it the image bytes.
  5. Call CreateMod and keep the id on the returned FCFCoreMod. Every later upload needs it.

FCreateModRequest, every field. Field names are the wire names.

FieldTypeDefaultNotes
classIdint6401130 (kNoUploadToClass) if the class disallows uploads
nameFStringempty1121 (kModNameAlreadyExists) if the name is taken
summaryFStringemptyShort text
descriptionTypeECFCoreMakrupTypePlainTextSee the markup tables below
descriptionFStringemptyInterpreted per descriptionType
primaryCategoryIdint640One category id
gameCategoryIdsTArray<int64>emptySerialized as one form field per element
isExperimentalboolfalseMarks the mod experimental
#include <cfcore_context.h>
#include <api/models/creation/create_mod_request.h>

void AMyModUploader::CreateModRecord() {
FCreateModRequest request;
request.classId = MyResolvedClassId;
request.name = TEXT("Frostfall Weather Pack");
request.summary = TEXT("Adds four winter weather states.");
request.descriptionType = ECFCoreMakrupType::Markdown;
request.description = TEXT("## Frostfall\nFour new weather states.");
request.primaryCategoryId = MyResolvedPrimaryCategoryId;
request.gameCategoryIds = { MyResolvedPrimaryCategoryId };

cfcore::CFCoreContext::GetInstance()->Creation()->CreateMod(
request, TEXT("C:/creators/frostfall/avatar.png"),
cfcore::ICFCoreCreation::FCreateModDelegate::CreateLambda(
[this](const TOptional<FCFCoreMod>& opt_mod,
const TOptional<FCFCoreError>& opt_err) {
if (opt_err.IsSet()) {
// code for local failures, apiError.errorCode for server failures.
UE_LOG(LogTemp, Error, TEXT("Create mod failed: %s (%d, api %d)"),
*opt_err.GetValue().description,
(int32)opt_err.GetValue().code,
opt_err.GetValue().apiError.errorCode);
return;
}
CreatedModId = opt_mod.GetValue().id; // Every later upload needs this.
})
);
}

Blueprint: Create New Mod, with the request built by MakeCreateModRequest.

warning

The avatar is required. The Blueprint node and the file-path C++ overload both read the avatar from disk first. A failed read gives ECFCoreErrorCodes::FileSystemError, description failed to read avatar from disk, and no request is sent. Only the buffer overloads treat the avatar as optional, attaching the binary part only when the buffer is valid and non-empty. From Blueprint, treat the avatar path as required.

Updating a mod record

FUpdateModRequest publicly derives from FCreateModRequest and adds nothing: same eight fields, same wire names.

warning

The update path uses the same CreateModRequestSerializer, which writes every field unconditionally. An update is a full replacement, not a patch: read the current values, change what you need, and send the whole struct back, or you blank every field left at its default. Read current values with Get My Mods (ICFCoreApi::Authorized()->GetMyMods), which returns every mod the signed-in player created or is a member of for the running game. Those FCFCoreMod objects carry no file information: for the files, take the ids and call Get Mods Info By Ids (ICFCoreApi::GetMods).

Blueprint: Update Existing Mod, with the request built by MakeUpdateModRequest.

Markup types for descriptions and changelogs

Two different enums with different value sets. They are not interchangeable: Text exists only on the changelog enum, PlainText only on the description enum. Both structs default to a usable value, so the failure mode is a wrongly rendered description, not a rejected upload.

ECFCoreMakrupType, used by descriptionType on both mod requests:

ValueNumericNotes
PlaceHolderDoNotUse0Do not send it
WysiwygHtml1
PlainText2The struct default
BBCode3
Creole4
Markdown5
RawHtml6
StandardBBCode8Note the gap: there is no value 7
RawCSS9

ECFCoreChangelogMarkupType, used by FCreateModFileRequest::changelogType:

ValueNumericNotes
PlaceHolderDoNotUse0Do not send it
Text1The struct default
HTML2
Markdown3

Uploading a mod file

How it works

  1. Have a mod id, from CreateMod or Get My Mods.
  2. Have the payload as one file on disk, or as a byte array in memory. Pack a directory tree first (see packaging below).
  3. Fill an FCreateModFileRequest.
  4. Call CreateModFile with the mod id, the request, and the payload.
  5. Drive your progress UI from the progress delegate and read the FUploadedModFile from the completion delegate.

FCreateModFileRequest, every field:

FieldTypeDefaultNotes
changelogTypeECFCoreChangelogMarkupTypeTextAlways sent
changelogFStringemptyAlways sent, even when empty
filenameFStringemptyAlways sent. See the auto-fill rule below
displayNameFStringemptyOmitted from the request entirely when empty
gameVersionIdsTArray<int64>emptyRepeated field. See the -1 rule below
releaseTypeECFCoreFileReleaseTypeReleaseNone 0, Release 1, Beta 2, Alpha 3
cookingOptionsFModFileCookingOptionssee cooking belowSent as JSON, and only when cookingOptions.isSourceFile is true
fileLengthint640Always sent. Mandatory for chunked uploads only. Overwritten from disk on the file-path overloads
isMarkedForManualReleaseboolfalseWhether the mod is manually or automatically published after moderation approval
warning

filename behaves differently on the two overloads. On the file-path overload an empty filename is filled from FPaths::GetCleanFilename of the local path. On the buffer overload it is not: an empty filename fails immediately with ECFCoreErrorCodes::MissingParameter, description Missing filename parameter, before any request is sent. The matching server error is 1133 (kNoFileName).

note

An empty gameVersionIds is not sent as an empty list. A single value of -1 is substituted instead, meaning take the latest version. To target specific versions, resolve real ids with Get Versions Info (ICFCoreApi::GetVersions, or GetVersionsDetailed for the detailed form). An unknown id returns 1137 or 1138; a valid id not permitted for your class returns 1139.

#include <api/models/creation/create_mod_file_request.h>
#include <api/models/creation/uploaded_mod_file.h>

void AMyModUploader::UploadModFile(int64 ModId) {
FCreateModFileRequest request;
request.changelogType = ECFCoreChangelogMarkupType::Markdown;
request.changelog = TEXT("- Added blizzard state\n- Fixed fog density");
request.displayName = TEXT("Frostfall 1.2.0");
request.releaseType = ECFCoreFileReleaseType::Release;
// filename and fileLength stay at their defaults: the file-path overload
// fills both from disk.

cfcore::CFCoreContext::GetInstance()->Creation()->CreateModFile(
ModId, request, TEXT("C:/creators/frostfall/Frostfall-1.2.0.zip"),
cfcore::ICFCoreCreation::FCreateModFileRequestIdDelegate::CreateLambda(
[this](int64 upload_id) {
// Arrives before any request is sent, and is NOT a valid cancel id.
ActiveCreationId = upload_id;
}),
cfcore::ICFCoreCreation::FFileTransferProgressDelegate::CreateLambda(
[this](const FFileTransferProgress& progress) {
// 0..100 across the whole file, not per chunk. Delivered on the game
// thread, so this can touch UMG widgets directly.
OnUploadProgress(progress.progress, progress.bytesPerSecond);
}),
cfcore::ICFCoreCreation::FCreateModFileDelegate::CreateLambda(
[this](const TOptional<FUploadedModFile>& opt_file,
const TOptional<FCFCoreError>& opt_err) {
if (opt_err.IsSet()) { return; }
// Keep fileId to attach cooked files to this source later.
UploadedSourceFileId = opt_file.GetValue().fileId;
})
);
}

Blueprint: Create Mod File, with the request built by MakeCreateModFileRequest.

The progress and uploaded-file models

FFileTransferProgress:

FieldTypeMeaning
progressint32Percentage, computed across the whole file
transferredBytesint64Total bytes transferred so far
bytesPerSecondint64Current transfer rate

FUploadedModFile, delivered on success. All three fields are read-only.

FieldTypeMeaning
fileIdint64The new file's id. This is the sourceFileId for any cooked file you attach later
directoryFStringServer-side directory
filenameFStringServer-side filename
warning

FUploadedModFile is not FFile. It carries no status, no hashes and no download URL. For processing and moderation state, or hashes, fetch the file with Get Files Info By Ids or Get Mods Info By Ids. On the chunked path it is populated from FModFileChunkedInfo before any bytes move (StartUploadingChunks copies fileId, directory and fileName up front) and FinalizeCreation returns that same struct on both success and failure, so a failed chunked upload can deliver both a populated FUploadedModFile and an error. Check the error first.

Chunked upload for large files

Chunking is not something you opt into from the game layer. It is the default behaviour of the file-path overloads.

Path you callWhat happensCeiling
ICFCoreCreation::CreateModFile with a local filenameAlways goes through the chunked service, even for a small file, which becomes a single chunkBounded by chunk count, not memory
ICFCoreCreation::CreateModFile with a bufferOne single POST, no chunkingSmaller files only, limited to 2^31 - 1 bytes, about 2 GB
ICFCoreApiCreation::CreateModFileChunked plus UploadModFileChunkYou drive each chunkFor large files from disk, for example larger than 1 GB

How it works

  1. The SDK stats the local file. An invalid stat fails the upload with ECFCoreErrorCodes::MissingFileInformation.
  2. fileLength on the request is overwritten with the real size on disk. Whatever you set is ignored on this path.
  3. The create-chunked call returns an FModFileChunkedInfo. An empty uploadId fails the upload with ECFCoreErrorCodes::ApiError, description Missing uploadId from server, and apiError.failedToParseServerResponse set.
  4. Total chunks is the file size divided by the chunk size, rounded up. The chunk index starts at 1.
  5. Each chunk is read from disk at its offset and posted with UploadModFileChunk. On success the index advances and the retry budget resets.
  6. When the current index equals the total, the upload is complete.
warning

The chunk size is 100 MB, from kMaxChunkSizeInBytes. Match it if you drive chunking yourself. A zero-byte file is not rejected up front: it passes the initial stat check as a valid, empty file, and the total-chunks calculation rounds up to zero. The first chunk read then asks for a full 100 MB chunk against an empty file, overruns it, and the upload finalizes with ECFCoreErrorCodes::FileSystemError instead. Check the file size yourself before starting an upload on a file that might be empty.

FModFileChunkedInfo, returned by CreateModFileChunked and CreateCookedModFileChunked. All read-only.

FieldTypeMeaning
fileIdint64The file id the chunks assemble into
directoryFStringServer-side directory
fileNameFStringServer-side filename
uploadIdFStringThe upload session handle. Feed into FModFileChunkMetadata::chunkedUploadId

FModFileChunkMetadata, sent with every chunk. All read-only, and the field names are the wire names. The chunk bytes go in the multipart field named file.

FieldTypeDefaultMeaning
chunkedUploadIdFStringemptyThe uploadId from FModFileChunkedInfo
totalChunksint320How many chunks the file is split into
chunkIndexint321One-based, not zero-based
note

chunkIndex is one-based, not zero-based.

Retry and failure behaviour on the SDK-driven chunked path:

BehaviourValue or rule
Retry budget per chunkStarts at 3 (kMaxUploadChunkRetries)
When a retry happensOnly when the API error has serverUnreachable set. Any other error fails the whole upload immediately
How the budget is spentHandleUploadChunkError decrements per serverUnreachable error and finalizes at 0, so a chunk gets its first attempt plus up to two re-attempts
Budget resetBack to 3 after every successfully uploaded chunk
On retry exhaustionThe whole creation is finalized with the API error
On a disk read failure mid-uploadFinalized with ECFCoreErrorCodes::FileSystemError
On SDK shutdownEvery in-flight creation is cancelled. CreateModFileChunkedServiceImpl subscribes to the lifetime shutdown event and cancels its whole in-progress map. Uploads finalize with ECFCoreErrorCodes::UploadCancelled
warning

A transient HTTP error that is not serverUnreachable, for example a token expiring mid-upload, is not retried. Your uploader needs a resume-from-scratch path, not just a spinner.

Driving chunks yourself, through the API layer:

#include <api/models/creation/mod_file_chunked_info.h>
#include <api/models/creation/mod_file_chunk_metadata.h>

void AMyModUploader::StartManualChunkedUpload(int64 ModId, int64 FileLength) {
FCreateModFileRequest request;
request.filename = TEXT("Frostfall-2.0.0.zip");
request.fileLength = FileLength; // Mandatory when you drive chunking.

cfcore::CFCoreContext::GetInstance()->Api()->Creation()->CreateModFileChunked(
ModId, request,
cfcore::ICFCoreApiCreation::FCreateModFileChunkedDelegate::CreateLambda(
[this](const TOptional<FModFileChunkedInfo>& opt_info,
const TOptional<FCFCoreApiResponseError>& opt_err) {
// Note the error type on this layer: FCFCoreApiResponseError.
if (opt_err.IsSet() || !opt_info.IsSet()) return;
if (opt_info.GetValue().uploadId.IsEmpty()) return;

FModFileChunkMetadata metadata;
metadata.chunkedUploadId = opt_info.GetValue().uploadId;
metadata.totalChunks = MyComputedTotalChunks;
metadata.chunkIndex = 1; // One-based.
UploadOneChunk(metadata);
})
);
}

void AMyModUploader::UploadOneChunk(const FModFileChunkMetadata& Metadata) {
// Offset is (chunkIndex - 1) * kMaxChunkSizeInBytes.
TSharedPtr<TArray<uint8>> chunk = ReadChunkFromDisk(Metadata.chunkIndex);

// The return value is the HTTP-layer id, and the only id
// ICFCoreApiCreation::CancelCreation accepts.
ActiveCancelId =
cfcore::CFCoreContext::GetInstance()->Api()->Creation()->UploadModFileChunk(
Metadata, chunk,
cfcore::ICFCoreApiCreation::FFileTransferProgressDelegate(),
cfcore::ICFCoreApiCreation::FUploadModFileChunkDelegate::CreateLambda(
[this, Metadata](const bool& success,
const TOptional<FCFCoreApiResponseError>& opt_err) {
if (opt_err.IsSet()) return; // Retry only on serverUnreachable.
if (Metadata.chunkIndex == Metadata.totalChunks) return; // Done.
FModFileChunkMetadata next = Metadata;
next.chunkIndex++;
UploadOneChunk(next);
})
);
}

Blueprint: no chunked nodes exist, and FModFileChunkMetadata is read-only to Blueprint, so hand-rolled chunking is C++ only. Create Mod File and Create Cooked Mod File get chunking transparently, so everything above applies to them even though their pins do not mention chunks.

Cooked and source uploads

Unreal content is cooked per target platform before a client can load it. The SDK models this as two file kinds attached to one mod, and a cooked file is always derived from an already-uploaded source file id.

ModelWhat you uploadWhat cooks itautoCookingType
Cloud cooking, all platformsOne source file, marked isSourceFileThe CurseForge backend, per its server-side configurationAll
Cloud cooking, PC onlyOne source file, marked isSourceFileThe backend, restricted to PC-supported platformsPCOnly
Manual cookingOne source file, then one cooked file per platformYou, on your own machines or build farmManual

FModFileCookingOptions, set on FCreateModFileRequest::cookingOptions:

FieldTypeDefaultMeaning
isSourceFileboolfalseSet true when uploading a source file for cooking. The rest of the options are ignored when it is false
autoCookingTypeECFCoreAutoCookingTypeAllUse this enum to support PCOnly or manual cooking
cookerVersionFStringemptyFor cloud cooking

ECFCoreAutoCookingType:

ValueNumericMeaning
All0Automatically cook based on server configurations
PCOnly1Only cook for PC supported platforms (based on server configurations)
Manual2Do not auto-cook for any platforms
warning

All does not mean every platform in ECFCorePlatform. It cooks based on server-side configuration resolved outside the SDK. Confirm your set in the Developer Portal at console.curseforge.com or with cfforstudios@overwolf.com before assuming console coverage.

Uploading a manually cooked file

How it works

  1. Upload the source with CreateModFile, cookingOptions.isSourceFile true and cookingOptions.autoCookingType set to Manual.
  2. Keep the fileId from the resulting FUploadedModFile. That is your sourceFileId.
  3. Cook the content yourself, once per target platform.
  4. For each platform, call CreateCookedModFile with the mod id, the sourceFileId, and an FCreateCookedModFileRequest naming that platform.

FCreateCookedModFileRequest:

FieldTypeDefaultNotes
platformECFCorePlatformNoneWhich platform this file was cooked for. Sent as the stripped display name, not the numeric
fileLengthint640Mandatory for chunked uploads only. Overwritten from disk on the file-path overload

ECFCorePlatform, the full set:

ValueNumericValueNumeric
None0Mac7
Windows1IOS8
XboxOne2TVOS9
XboxXS3Android10
Linux4Switch11
PS45WindowsServer12
PS56LinuxServer13
warning

WindowsServer and LinuxServer are distinct from Windows and Linux. If your game ships dedicated servers that load UGC, a client-cooked Windows file is not a server file. Cook and upload both.

warning

The enum containing a platform does not mean your game may publish for it. 1160 (kPlatformNotAllowed) is a configuration issue, not a code bug: raise it with cfforstudios@overwolf.com. See also platforms support and Xbox and PlayStation compliance.

#include <api/models/creation/create_cooked_mod_file_request.h>
#include <api/models/enums/platform.h>

// Step 1: upload the source, flagged for manual cooking.
void AMyModUploader::UploadSourceForManualCooking(int64 ModId) {
FCreateModFileRequest request;
request.displayName = TEXT("Frostfall 2.1.0");
request.releaseType = ECFCoreFileReleaseType::Release;
request.cookingOptions.isSourceFile = true; // Required, or the rest is dropped.
request.cookingOptions.autoCookingType = ECFCoreAutoCookingType::Manual;

cfcore::CFCoreContext::GetInstance()->Creation()->CreateModFile(
ModId, request, TEXT("C:/creators/frostfall/src/Frostfall-2.1.0.zip"),
cfcore::ICFCoreCreation::FCreateModFileRequestIdDelegate(),
cfcore::ICFCoreCreation::FFileTransferProgressDelegate(),
cfcore::ICFCoreCreation::FCreateModFileDelegate::CreateLambda(
[this, ModId](const TOptional<FUploadedModFile>& opt_file,
const TOptional<FCFCoreError>& opt_err) {
if (opt_err.IsSet()) return;
UploadCookedForPlatform(ModId, opt_file.GetValue().fileId,
ECFCorePlatform::Windows);
})
);
}

// Step 2: one call per platform, against the source file id.
void AMyModUploader::UploadCookedForPlatform(int64 ModId, int64 SourceFileId,
ECFCorePlatform Platform) {
FCreateCookedModFileRequest request;
request.platform = Platform; // fileLength is filled from disk.

cfcore::CFCoreContext::GetInstance()->Creation()->CreateCookedModFile(
ModId, SourceFileId, request,
TEXT("C:/creators/frostfall/cooked/Win/Frostfall-2.1.0.zip"),
cfcore::ICFCoreCreation::FCreateModFileRequestIdDelegate(),
cfcore::ICFCoreCreation::FFileTransferProgressDelegate(),
cfcore::ICFCoreCreation::FCreateModFileDelegate::CreateLambda(
[](const TOptional<FUploadedModFile>& opt_file,
const TOptional<FCFCoreError>& opt_err) {
if (opt_err.IsSet()) {
UE_LOG(LogTemp, Error, TEXT("Cooked upload failed: %s (api %d)"),
*opt_err.GetValue().description,
opt_err.GetValue().apiError.errorCode);
}
})
);
}

Blueprint: Create Cooked Mod File, with the two-member request built by a Make Struct node.

Server errors specific to the source-and-cooked relationship:

CodeConstantWhat it means
1103kNoSuchModFileAn uploaded mod file failed (internal server error), or a cooked file was uploaded for an unknown sourceFileId
1105kModFileSourceMismatchThe cooked file does not match the named source
1106kNotSourceModFileThe id you passed as sourceFileId is not a source file
1107kModFileAuthorMismatchOnly the source file author can upload cooked files associated to that source
1131kFileIsStillProcessingThe file is not yet in a state that accepts the next step
1143kSourceFileNotInCookingStatusThe source file is not in a cooking state
1144kNotAllSourceFilesReadyOne or more source files are not ready
1145kSourceFileAlreadyHasPlatformA cooked file for that source and platform already exists
warning

1107 constrains your architecture, not just your call. If a build machine uploads cooked files, the same authenticated identity that uploaded the source must upload them. Do not split source upload and cooked upload across two accounts.

Reading back the cooker version. FFile::cookingInfo is an FFileCookingInfo whose only member is a read-only cookerVersion string, used for cloud cooking. In C++, call ICFCoreApi::GetFiles with an FCFCoreGetFilesFilter whose fileIds holds your file. Blueprint: Get Files Info By Ids (category cfcore|Api, pins FileIds, OnResults, OnError), then break the returned FFile.

Packaging and hashing

A mod file upload takes one file, not a directory. The SDK ships a zip helper so your uploader needs no third-party dependency. Zip first, then pass the resulting path to Create Mod File. The compression service is documented in full on Game info and platforms.

Reach the service in C++ through ICFCore::Utils, then ICFCoreUtils::Compression, which returns an ICompressionService*:

TFuture<ECompressionError> Zip(
const TSharedRef<TArray<FString>> files_to_zip,
const FString& output_zip_file,
FProgressDelegate on_progress);

Blueprint: Zip Paths, in the category cfcore|Utils|Compression.

PinType
InPathsToZipPaths to add to the archive
InOutputZipFileDestination archive path
OnProgressFUtilsCompressionProgressDelegate
OnSuccessFCFCoreSuccessDelegate
OnErrorFUtilsCompressionErrorDelegate

FCompressionProgress carries progress (int32, percentage) and file (FString, the file being processed). ECompressionError: None 0, FailedToReadZip 1, FailedToExtractFile 2, FailedToWriteFile 3.

warning

The zip helper does not report through FCFCoreError. In C++ the result arrives as an ECompressionError on the returned TFuture, and in Blueprint on OnError as an ECompressionError. Check for ECompressionError::None rather than looking for an ECFCoreErrorCodes value.

Hashing. Hashes are a verification surface, not an upload parameter. Hashes come back on the file model.

TypeFieldMeaning
FFilehashes (TArray<FFileHash>)Server-supplied hashes for the file
FFileHashvalue (FString)The hash string
FFileHashalgo (ECFCoreHashAlgo)Which algorithm produced it. None 0, Sha1 1, Md5 2
FFilefileFingerprint (int64)A separate numeric fingerprint, distinct from the hashes array
note

The SDK's own install pipeline reads hashes[0].algo and hashes[0].value, computes the matching digest over the local file, and fails installation with ECFCoreErrorCodes::DownloadedFileHasInvalidHash on a mismatch. It returns that same code when the algo is neither Sha1 nor Md5, and skips verification with a warning when hashes is empty. If you build your own post-upload verification, handle the empty array explicitly and read algo per entry rather than assuming one algorithm.

After the upload completes

A successful upload is not a published file. isMarkedForManualRelease on FCreateModFileRequest controls the publishing step: leave it false for automatic publishing after moderation approval, set it true when a content drop has to land alongside a game patch. For what happens next, see moderation and testing and launching your game.

The status enums are ECFCoreFileStatus on FFile::fileStatus and ECFCoreModStatus on FCFCoreMod::status, with full value lists in cloud cooking references. Poll with Get Files Info By Ids (ICFCoreApi::GetFiles) rather than treating a single non-Released status as a failure.

Error reference for creation

FCFCoreError carries isError, a code of type ECFCoreErrorCodes, a nested apiError of type FCFCoreApiResponseError, and a description. Check code for local failures and apiError.errorCode for server failures. When an FCFCoreError is built from a server response, code is always ECFCoreErrorCodes::ApiError and the description is copied from the server error.

Local ECFCoreErrorCodes values from the Creation section:

CodeWhen it firesFix
FailedToInitializeAny Creation node on UCFCoreSubsystem when the SDK is not initialized. Description Not initializedInitialize first
UserNotAuthenticatedEvery Creation call that reaches the network, before any request is sentAuthenticate first
MissingParameterCreateModFile buffer overload with an empty filename. Description Missing filename parameterSet filename explicitly on the buffer overload
FileSystemErrorAvatar read failure (failed to read avatar from disk), or a chunk read failure mid-uploadVerify the path is readable and not locked
MissingFileInformationThe local file could not be stat'ed. Description Please assure the local file exists on disk ({0}) on the mod-file path, none on the cooked pathVerify the path before calling
ApiErrorChunked create returned an empty uploadId (description Missing uploadId from server, apiError.failedToParseServerResponse set), and every server-side failureRetry; escalate to cfforstudios@overwolf.com if it persists
UploadCancelledThe upload was cancelled, including by SDK shutdownExpected on teardown

FCFCoreApiResponseError also carries errorCode (int32), description (FString), and these booleans: cancelled, badRequest, entityNotFound, serverUnreachable, missingPrivileges, tokenExpired, resourceExpired, failedToParseServerResponse.

warning

serverUnreachable is the only flag the chunked uploader treats as retryable. Mirror that in your own retry logic, and surface tokenExpired as a re-authentication prompt rather than a generic failure.

Numeric server codes in apiError.errorCode, from cfcore::api_error_codes. They are not ordered in any meaningful sequence. The source-and-cooked codes are in the table above; these are the rest that Creation returns.

CodeConstantMeaning
0kNoneNo error
1100kNoSuchModUploading a file for, or updating, a mod that does not exist
1102kNoSuchGameCreating a mod with an unknown game id
1104kModGameMismatchUploading to a mod belonging to a different game than the SDK was initialized with
1110kUserNotAllowedForModThe authenticated account may not upload or update this mod
1111kDeletedUserSuspectedAccount state issue
1120kModIsNotValidThe mod is not in a valid state
1121kModNameAlreadyExistsThe mod name is already taken
1130kNoUploadToClassThe classId is configured to not allow uploads
1132kFileSizeTooBigExceeded the maximum file size
1133kNoFileNameMissing filename for the uploaded file
1134kNoAdditionalFilesNo additional files
1135kParentFileDoesNotExistThe parent file does not exist
1136kGameVersionsNotAllowedForAddonFilesGame versions are not allowed for this file kind
1137kNoSuchGameVersionUnknown game version. Re-resolve from the versions API
1138kInvalidGameVersionUnknown game version. Re-resolve from the versions API
1139kGameVersionNotMappedToCategoryThe game version is not allowed for the given classId
1140kNoChildFileTypeNo child file type
1141kUserCannotUploadMemberAccessOnlyContentThe authenticated account may not upload membership files (CurseForge Pro)
1142kUserCannotUploadFilesThe authenticated account lacks privileges to upload a file for this mod
1160kPlatformNotAllowedThe target platform is not permitted
warning

Three of these are permission errors that look like code errors: 1110, 1142, and 1107 above. None are fixed by changing your request. Check the account role on the mod in the Developer Portal at console.curseforge.com, then escalate to cfforstudios@overwolf.com.

Next steps

  • To let players find and install what mod authors uploaded, see the in-game mod browser and mod installation methods.
  • To confirm your class ids, category ids, and permitted cooking platforms, open the Developer Portal at console.curseforge.com.
  • For file size limits, cooking platform enablement, permission errors on a mod, the status state machines, or the current state of cf-ue-editor-plugin, contact cfforstudios@overwolf.com.