[SalesForce] How to set the ContentVersion.FileType when creating new File via Apex

I'm working on a webservice to create new Salesforce Files. To validate the underlying methods, I manually uploaded a PNG file, which shows up in the file preview in Files.

I can retrieve the ContentVersion record via SOQL and create a new File, which does open correctly as an image if I download it and specify the application, but the image does not appear in the file preview. FileType is determined based on ContentURL and/or(?) PathOnClient, but these require the ContentVersion.Id, which doesn't exist until after the file is created, and cannot be edited.

Given a PNG with id 'originalImageId', I can retrieve attributes using the following:

String myId = 'originalImageId';
ContentVersion cv = 
    [SELECT Title, VersionData, ContentDocumentId, 
            ContentLocation, FileType  
     FROM ContentVersion 
     WHERE ContentDocumentId = :myId 
     AND IsLatest = true];

String encodedBlob = EncodingUtil.base64Encode(cv.VersionData);
String newId = FileManager.createFile(cv.Title, encodedBlob, cv.FileType);

which calls

public class FileManager {

  public static Id createFile(String fileName, String base64Data,
                             String fileType, Id contentDocumentId){

    base64Data  = EncodingUtil.urlDecode(base64Data, 'UTF-8')                                 ;

    ContentVersion cv = new ContentVersion();
    cv.ContentLocation = 'S'; // 'S' = a Salesforce File

    cv.ContentDocumentId = contentDocumentId;        

    cv.VersionData = EncodingUtil.base64Decode(base64Data);
    cv.Title = fileName;
    // path will be different for Production
    String path = 'https://nxstage--qa--c.cs92.content.force.com/sfc/servlet.shepherd/version/download';                             
    cv.PathOnClient = path;

    Utils.DatabaseInsert(cv);

    Utils.debug('The contentDocumentId for ' + fileName + ' is ' + cv.ContentDocumentId);

    return cv.Id;
 }

 public static Id createFile(String fileName, String base64Data,
                             String fileType){

    String newId = createFile(fileName, base64Data, fileType, null);
    Utils.debug('The ContentVersion.Id of ' + fileName + ' is ' + newId);

    return newId;
 }

Is there a trick to setting PathOnClient at create time / on insert that will allow me to specify the filetype?

Best Answer

OK, I got this to work. The key to getting the FileType set correctly and thus a preview generated is to ensure that the proper file extension is set via ContentVersion.PathOnClient.

Additionally, while it may be necessary in your final web service, the base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8') creates a bad version of the file data when used via anon apex as in your example. I figured this out because when I downloaded the preview-less files, they wouldn't open as images.

I changed the signatures, queries and calls to include ContentVersion.FileExtension, and then set PathOnClient to be Title + '.' + FileExtension as I said in my comment above.

The resulting FileManager class is then:

public class FileManager {

  public static Id createFile(String fileName, String base64Data,
                             String fileType, String fileExt, Id contentDocumentId){

    //base64Data  = EncodingUtil.urlDecode(base64Data, 'UTF-8')                                 ;

    ContentVersion cv = new ContentVersion();
    cv.ContentLocation = 'S'; // 'S' = a Salesforce File

    cv.ContentDocumentId = contentDocumentId;        

    cv.VersionData = EncodingUtil.base64Decode(base64Data);
    cv.Title = fileName;
    cv.PathOnClient = fileName + '.' + fileExt;

    //Utils.DatabaseInsert(cv);
    insert cv;

    System.debug('The contentDocumentId for ' + fileName + ' is ' + cv.ContentDocumentId);

    return cv.Id;
 }

 public static Id createFile(String fileName, String base64Data,
                             String fileType, String fileExt){

    String newId = createFile(fileName, base64Data, fileType, fileExt, null);
    System.debug('The ContentVersion.Id of ' + fileName + ' is ' + newId);

    return newId;
 }
}

And the anonymous block:

String myId = '069nnnnnnnnnnnnnnn';
ContentVersion cv = 
    [SELECT Title, FileExtension, VersionData, ContentDocumentId, 
            ContentLocation, FileType  
     FROM ContentVersion 
     WHERE ContentDocumentId = :myId 
     AND IsLatest = true];

String encodedBlob = EncodingUtil.base64Encode(cv.VersionData);
String newId = FileManager.createFile(cv.Title, encodedBlob, cv.FileType, 
    cv.FileExtension);