This page walks through the full process of uploading a file to S-Drive from your own custom Apex/client code, without using the S-Drive UI. Each step links to its own reference page with full parameter details; this page is about how they fit together, plus one complete worked example.
How to upload files
SDriveTools never uploads your file's bytes for you. It creates and tracks the Salesforce-side record, and hands you signed credentials so your own code — usually client-side JavaScript, since Apex callouts have size and time limits unsuited to large files — can send the bytes directly to the storage bucket. You then tell Salesforce the upload is finished.
Additional documentation
https://cyangate.atlassian.net/wiki/x/0IBvt - used for all uploads
https://cyangate.atlassian.net/wiki/x/7YBvt - used for multipart uploads
https://cyangate.atlassian.net/wiki/x/_IBvt - used for multipart uploads
https://cyangate.atlassian.net/wiki/x/EIFvt - used for multipart uploads
https://cyangate.atlassian.net/wiki/x/BIFvt - used for all uploads
The two paths - simple or multipart
Every upload starts the same way and ends the same way. The only difference is how you get the bytes into storage in the middle: one direct upload for smaller files, or a multipart upload (many chunks) for larger ones.
The pieces
|
Step |
Method |
What it does |
|---|---|---|
|
1 |
|
Validates the file, creates its Salesforce "Work In Progress" record, returns signed upload credentials. |
|
2.1 (large files only) |
|
Starts a multipart upload session, returns an |
|
2.2 (large files only, repeated) |
|
Registers each uploaded chunk as a part of the multipart session. |
|
2.3 (large files only) |
|
Assembles all parts into the final object in storage. |
|
3 (always, last) |
|
Marks the Salesforce record as finished. Required on both paths. |
|
optional |
|
Signed headers for making your own direct authenticated requests to storage (e.g. to verify or fetch a file), if you need it. |
If something fails partway through and you need to abandon the upload rather than finish it, see cancelUpload (cleans up a WIP record) and abortMultiPartUpload (cancels an in-progress multipart session) — documented on their own pages.
(This page intentionally stops at "the file exists in S-Drive." Sharing behavior, activity logging, and shortcuts are separate topics covered elsewhere.)
Choosing simple vs. multipart
There's no size threshold enforced by SDriveTools itself — it's a decision you make based on your own constraints. A single direct upload is simpler and is fine for most everyday file sizes. Reach for the multipart flow when a file is large enough, or the connection unreliable enough, that you'd rather upload it in independently-retryable chunks than risk resending the whole thing on failure. If you do go multipart, S3-backed orgs allow up to 10,000 parts per file (check maxPartCount on the UploadRequestInfo from initializeUpload for the exact limit on your org, since it differs for GCS).
Form fields for the direct upload
When you POST the file directly to storage (the simple-upload path), S3 checks every form field you send against the signed policy from initializeUpload — a field that isn't part of that policy, or doesn't match it, gets the whole request rejected. Here's what to send and where each value comes from:
|
Form field |
Value |
Notes |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
S3-backed orgs only. GCS-backed orgs ( |
|
|
|
|
|
|
|
Only include this if |
|
|
|
Only include this if |
|
|
|
Optional. Without it, S3 returns |
|
|
e.g. |
Optional, and only usable if you added a matching condition to |
|
|
the file's raw bytes |
Must be the last field in the multipart body — an S3 requirement, not an S-Drive one. |
Full example: simple upload (small file)
// STEP 1 — Prepare the upload: creates the WIP record and returns signed credentials
Blob fileBody = /* your file's bytes */;
cg__CaseFile__c fileRecord = new cg__CaseFile__c(
cg__File_Name__c = 'invoice.pdf',
cg__File_Size_in_Bytes__c = fileBody.size(),
cg__Case__c = caseId,
cg__Parent_Folder_Id__c = null // null = uploading to the case's root, not a subfolder
);
List<UploadRequestInfo> uploadInfos = SDriveTools.initializeUpload(
caseId,
new List<SObject>{ fileRecord },
(Map<String,String>) null
);
UploadRequestInfo info = uploadInfos[0];
// STEP 2 — Upload the actual bytes directly to storage.
// This example uses Apex's Http class to illustrate the mechanics; in practice, many
// implementations do this step from client-side JavaScript instead (a Visualforce/LWC
// page posting directly to storage), to avoid Apex callout size and time limits.
String boundary = '----SDriveBoundary' + String.valueOf(Crypto.getRandomInteger());
String endpoint = 'https://' + info.bucketName + '.' + info.s3Endpoint;
// Build the form fields — see the table above for what each one means and which are conditional.
// Field order matters, and 'file' must be last.
Map<String,String> formFields = new Map<String,String>{
'key' => info.fileLocation,
'Content-Type' => info.fileType,
'policy' => info.policy,
'x-amz-credential' => info.awsCredential,
'x-amz-date' => info.timeStamp,
'x-amz-algorithm' => 'AWS4-HMAC-SHA256', // GCS-backed orgs use different, x-goog-* fields — not covered here
'x-amz-signature' => info.signature,
'success_action_status' => '201' // optional — without it, S3 returns 204 No Content instead
};
// Only include 'acl' if the bucket doesn't control its own ACL
if (!info.bucketControlledAcl) {
formFields.put('acl', 'private');
}
// Only include server-side encryption if this org is configured to use it
if (String.isNotBlank(info.s3EncryptionType)) {
formFields.put('x-amz-server-side-encryption', info.s3EncryptionType);
}
// Content-Disposition is optional, and only usable if you added a matching condition
// to policyMap when you called initializeUpload — otherwise S3 rejects the request
// as violating the signed policy. Omitted here since this example didn't set one.
Blob requestBody = buildMultipartBody(formFields, boundary, 'file', fileRecord.cg__File_Name__c, fileBody); // your own helper — 'file' must be the LAST field
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint);
req.setMethod('POST');
req.setHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
req.setBodyAsBlob(requestBody);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 201) {
throw new SDriveException('Upload to storage failed: ' + res.getBody());
}
// STEP 3 — Tell Salesforce the upload is finished (required — don't skip this)
List<ResultObject> results = SDriveTools.completeUpload(new List<Id>{ info.wipFileId });
for (ResultObject r : results) {
if (r.status == 'fail') {
System.debug(LoggingLevel.ERROR, 'Failed to finalize upload: ' + r.errorMessage);
}
}
Full example: multipart upload (large file)
// STEP 1 — Same as above: creates the WIP record and returns signed credentials
List<UploadRequestInfo> uploadInfos = SDriveTools.initializeUpload(
caseId,
new List<SObject>{ largeFileRecord },
(Map<String,String>) null
);
UploadRequestInfo info = uploadInfos[0];
// STEP 2 — Start the multipart session
String uploadId = SDriveTools.initializeMultiPartUpload(info.fileLocation);
// STEP 3 — Upload each chunk, then register it as a part
List<String> partETags = new List<String>();
Integer partNumber = 1;
for (Blob chunk : splitIntoChunks(largeFileBody)) { // your own chunking logic — 5MB minimum per part except the last
String tempKey = info.fileLocation + '.' + (partNumber - 1); // note the off-by-one
putBytesDirectlyToStorage(tempKey, chunk); // your own direct PUT of this chunk's bytes to storage
String eTag = SDriveTools.copyPartMultiPartUpload(info.fileLocation, uploadId, partNumber);
partETags.add(eTag);
partNumber++;
}
// STEP 4 — Assemble all parts into the final object
SDriveTools.completeMultiPartUpload(info.fileLocation, uploadId, partETags);
// STEP 5 — Same required final step as the simple path — don't skip this
List<ResultObject> results = SDriveTools.completeUpload(new List<Id>{ info.wipFileId });
for (ResultObject r : results) {
if (r.status == 'fail') {
System.debug(LoggingLevel.ERROR, 'Failed to finalize upload: ' + r.errorMessage);
}
}
buildMultipartBody,putBytesDirectlyToStorage, andsplitIntoChunksare illustrative helper functions, not part of SDriveTools — you'll implement the actual HTTP mechanics (or file-chunking logic) yourself, in Apex or, more commonly for larger files, in client-side JavaScript.