S-Drive 3.6 Documentation

Uploading Files to S-Drive

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.


e218e059-0184-4abb-99c0-f481185dab47



The pieces

Step

Method

What it does

1

initializeUpload

Validates the file, creates its Salesforce "Work In Progress" record, returns signed upload credentials.

2.1 (large files only)

initializeMultiPartUpload

Starts a multipart upload session, returns an uploadId.

2.2 (large files only, repeated)

copyPartMultiPartUpload

Registers each uploaded chunk as a part of the multipart session.

2.3 (large files only)

completeMultiPartUpload

Assembles all parts into the final object in storage.

3 (always, last)

completeUpload

Marks the Salesforce record as finished. Required on both paths.

optional

getAmazonHeaders

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

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'

S3-backed orgs only. GCS-backed orgs (info.storageService == 'gcs') use different, x-goog-* fields instead — not covered on this page.

x-amz-signature

info.signature


acl

'private'

Only include this if info.bucketControlledAcl is false. If it's true, leave the field out entirely — the bucket controls its own ACL, and including it will fail the policy check.

x-amz-server-side-encryption

info.s3EncryptionType

Only include this if info.s3EncryptionType is populated — it's only set when the org uses non-default encryption. Use the actual returned value; don't hardcode 'AES256', since a different encryption type may be configured.

success_action_status

'201'

Optional. Without it, S3 returns 204 No Content on success with no body. Setting this gets you a 201 response with an XML body (bucket/key/ETag) if you want to parse it.

Content-Disposition

e.g. 'attachment; filename="' + info.fileName + '"'

Optional, and only usable if you added a matching condition to policyMap when you called initializeUpload — the signed policy doesn't allow this field by default, so including it without that will make S3 reject the request. See "Setting a custom Content-Disposition (or other) policy condition" on initializeUpload for exactly how to add that condition..

file

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, and splitIntoChunks are 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.