---
language: "en"
---
# 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](https://docs.sdriveapp.com/3.6/initializeupload.md) - used for all uploads

[https://cyangate.atlassian.net/wiki/x/7YBvt](https://docs.sdriveapp.com/3.6/initializemultipartupload.md) - used for multipart uploads

[https://cyangate.atlassian.net/wiki/x/_IBvt](https://docs.sdriveapp.com/3.6/copypartmultipartupload.md) - used for multipart uploads

[https://cyangate.atlassian.net/wiki/x/EIFvt](https://docs.sdriveapp.com/3.6/completemultipartupload.md) - used for multipart uploads

[https://cyangate.atlassian.net/wiki/x/BIFvt](https://docs.sdriveapp.com/3.6/completeupload.md) - 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](https://docs.sdriveapp.com/__attachments/a_8c7e68e16fd55110871b19cea3e93d1d9e31f2548105e574c37c6c17ab261854/e218e059-0184-4abb-99c0-f481185dab47?cb=8f914942a0310420c77d314eb52bae2e)

### 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.