This post describes how to leverage Dataverse in order to upload large files from a Dynamics 365 form to a server. Specifically, how to leverage the Dataverse File column to store the large file
Problem Description

Referring to Figure 1,
1.) A large file is placed on a Dynamics 365 form (assuming the internet browser used can handle the large file). This is achieved via a ‘file upload’ pcf control on the form (it’s not described in this post)
2.) Within the form’s custom TypeScript, the file is converted into base64, placed in a JSON request and sent to the custom API (i.e. C# plugin class) DocumentInExternalSystem (see appendix)
This works fine for files under 100MB. However the power platform rejects files over 100MB (well, it seems to be around this value) and generates the error message ‘The page was not displayed because the request entity is too large’.
The next section presents a solution for uploading files which are over 100MB
Solution Overview

Referring to Figure 2,
1.) A large file (over 100MB) is uploaded to a Dynamics 365 form
2.) Within the form’s custom TypeScript ‘uploadFile()’:
- a.) a call is made to generate a record in a custom dataverse table ‘File Staging’. (This table contains a File column to temporarily store the large file)
- b.) the record id of the record just created is retrieved
- c.) the large file is stored in the cpl_temporaryfile column of the ‘File Staging’ record
2.) Rather than the base64 file contents (of the large file) being placed in the JSON request (which would cause a ‘file too large’ error), the id of the newly created ‘File Staging’ record is placed in the request
3.) Within the custom API ‘DocumentInExternalSystem’ class, the C# function ‘DownloadFile()’ is called. This retrieves the file from the column cpl_temporaryfile in the ‘File Staging’ record
4.) Since the complete file is now available within the custom API, the file is deleted from dataverse
Solution Detail
1.) Create the table called ‘File Staging’ in dataverse with a File column called ‘Temporary File’ to temporarily store the large file

In this example, the size of the File column is 500MB. This can’t be selected within the designer, so the following (unsupported) steps need to be taken. (the same can be achieved with a http Post)
- Create an unmanaged solution containing the newly created table and File column
- Export it as an unmanaged solution
- In Customizations.xml, update the maximum file size to 524288 Kb (500 x 1024 x 1024)
- In Solution.xml, update the solution version
- Re-import the unmanaged solution

2.) Create the uploadFile() TypeScript function. (Note: this function calls the OOTB actions IntializeFileBlocksUpload, UploadBlock & CommitFileBlockUpload.)
private async uploadFile(fileModel: FileModel): Promise<string> {
const chunkSize = 4 * 1024 * 1024; //4MB
const chunks = Array.from({ length: Math.ceil(fileModel.content.length / chunkSize) }, (_, index) =>
fileModel.content.slice(index * chunkSize, (index + 1) * chunkSize)
);
const base64Guids = [];
const record: any = {
cpl_name: fileModel.name
};
let fileStagingId = "";
for (let i = 0; i < chunks.length; i++) {
base64Guids[i] = btoa(newGuid());
}
//----------------------------------------------------------CreateRecord---------------------------------
let fileStaging = await Xrm.WebApi.createRecord("cpl_fileStaging", record);
fileStagingId = fileStaging.id;
//----------------------------------------------------------InitializeFileBlocksUpload-------------------
var execute_InitializeFileBlocksUpload_Request = {
Target: { "@odata.type": "Microsoft.Dynamics.CRM.cpl_fileStaging", cpl_fileStagingid: fileStagingId },
FileName: fileModel.name,
FileAttributeName: "cpl_temporaryfile",
getMetadata: function () {
return {
boundParameter: null,
parameterTypes: {
Target: { typeName: "mscrm.crmbaseentity", structuralProperty: 5 },
FileName: { typeName: "Edm.String", structuralProperty: 1 },
FileAttributeName: { typeName: "Edm.String", structuralProperty: 1 }
},
operationType: 0, operationName: "InitializeFileBlocksUpload"
};
}
};
Xrm.WebApi.online.execute(execute_InitializeFileBlocksUpload_Request).then(
function success(response) {
if (response.ok) { return response.json(); }
}
).then(function (responseBody) {
let result = responseBody;
let y = 0;
let filecontinuationtoken = result["FileContinuationToken"];
//------------------------------------------------------UploadBlock----------------------------------
for (let i = 0; i < chunks.length; i++) {
let parameters: any = {};
parameters.BlockId = base64Guids[i];
parameters.BlockData = chunks[i];
parameters.FileContinuationToken = filecontinuationtoken;
fetch(Xrm.Utility.getGlobalContext().getClientUrl() + "/api/data/v9.2/UploadBlock", {
method: "POST",
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json"
},
body: JSON.stringify(parameters)
}).then(
function success(response) {
if (response.ok) {
//--------------------------------------CommitFileBlocksUpload-----------------------
y++;
if (y == chunks.length) {
let parameters: any = {};
parameters.FileName = fileModel.name;
parameters.MimeType = fileModel.mimeType;
parameters.BlockList = base64Guids;
parameters.FileContinuationToken = filecontinuationtoken;
fetch(Xrm.Utility.getGlobalContext().getClientUrl() + "/api/data/v9.2/CommitFileBlocksUpload", {
method: "POST",
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json"
},
body: JSON.stringify(parameters)
}).then(
function success(response) {
return response.json().then((json) => { if (response.ok) { return [response, json]; } else { throw json.error; } });
}
).then(function (responseObjects) {
let responseBody = responseObjects[1];
}).catch(function (error) {
console.error(error.message);
});
}
//CommitFileBlocksUpload end
} else {
return response.json().then((json) => { throw json.error; });
}
}
).catch(function (error) {
console.error(error.message);
});
}
//Upload Block end
}).catch(function (error) {
console.log(error.message);
});
return fileStagingId;}
3.) Create the DownloadFile() C# function. This function calls the actions InitializeFileBlockDownload & DownloadBlock
private static byte[] DownloadFile(PluginProcessService processService, IOrganizationService service, EntityReference entityReference, string attributeName)
{
InitializeFileBlocksDownloadRequest initializeFileBlocksDownloadRequest = new InitializeFileBlocksDownloadRequest()
{
Target = entityReference,
FileAttributeName = attributeName
};
int numberoOfTries = 0;
int maxReentries = 40;
bool success = false;
var initializeFileBlocksDownloadResponse = new InitializeFileBlocksDownloadResponse();
while ((numberoOfTries < maxReentries) && !success)
{
try
{
initializeFileBlocksDownloadResponse = (InitializeFileBlocksDownloadResponse)service.Execute(initializeFileBlocksDownloadRequest);
success = true;
}
catch
{
numberoOfTries++;
}
finally
{
Thread.Sleep(1000);
}
}
string fileContinuationToken = initializeFileBlocksDownloadResponse.FileContinuationToken;
long fileSizeInBytes = initializeFileBlocksDownloadResponse.FileSizeInBytes;
List<byte> fileBytes = new List<byte>((int)fileSizeInBytes);
long offset = 0;
long blockSizeDownload = 4 * 1024 * 1024; //4MB
if (fileSizeInBytes < blockSizeDownload)
{
blockSizeDownload = fileSizeInBytes;
}
while (fileSizeInBytes > 0)
{
DownloadBlockRequest downLoadBlockRequest = new DownloadBlockRequest()
{
BlockLength = blockSizeDownload,
FileContinuationToken = fileContinuationToken,
Offset = offset
};
var downloadBlockResponse = (DownloadBlockResponse)service.Execute(downLoadBlockRequest);
fileBytes.AddRange(downloadBlockResponse.Data);
fileSizeInBytes -= (int)blockSizeDownload;
offset += blockSizeDownload;
}
return fileBytes.ToArray();
}
4.) Create the DeleteFile() C# function. The code for this function is listed in Reference #2
For Part 2 Download large files via Dataverse
Appendix
The custom API DocumentInExternalSystem is defined as follows
There are two input parameters
- Document Request
- Document Request Type
The ‘Document Request’ contains the following FileModel class (which is passed to the custom API as a JSON)
- relatedEntityName
- relatedEntityId
- files
- content (contains the base64 encoded file when the file is under 100MB)
- dcpGenerated
- documentGenerationMethod
- extension
- fileStagingId
- lastModifiedDate
- mimeType
- name
- securityClassification
- size
- sizeInBytes
The Document Request Type can contain the following values
- UploadDocument (this calls the ‘DownloadFile’ class described above)
- DownloadDocument (described in Download large files via Dataverse)
- GetRelatedEntities (not described in this post)
References
1.) https://learn.microsoft.com/en-us/power-apps/developer/data-platform/file-attributes?tabs=sdk
2.) https://learn.microsoft.com/en-us/power-apps/developer/data-platform/file-column-data?tabs=sdk