Create a CloudPages form with an image/file upload option

The HTML <input> tag specifies an input field where the user can enter data. An input field can vary in many ways, depending on the type attribute. The <input> elements with type="file" let the user choose one or more files from their device storage. The file can then be manipulated using JavaScript in order to process it further.

In this article, we will focus on creating a CloudPages form with an image input field, and uploading that image into Marketing Cloud’s Content Builder. The high-level outline of this process, which can be found in this Stack Exchange post, is: once the file has been uploaded via the form, it needs to be Base64 encoded, and then passed in a REST API call to the /asset/v1/content/assets route in order to create the image in Content Builder.

We will need to create two separate CloudPages: the first one will run client-side JavaScript in order to encode the file into a Base64 string and it will then pass it to the second CloudPage, which will run server-side JavaScript to make an API call to create a new asset in Content Builder.

I will break it down into smaller parts to make it easier to understand the different steps along the way.

Create an input field

Let’s start with creating an <input> field on a CloudPage. The input needs to be of the type="file" in order to allow the end-user to choose a file for upload from their local machine. We will also add an accept attribute, which specifies a filter for what file types the user can pick from the file input dialogue box. For our use case, it will be image/*: [see code snippet]

Please select a file to upload:
<br>
<input id="file" type="file" accept="image/*">
<br>
<button id="button">Upload</button>

We also need to include an id attribute in order to be able to reference the input field in JavaScript.

Encode the image using Base64

Base64 is a group of binary-to-text encoding schemes which enable storing and transferring data over media that are designed to deal with ASCII (text) only. In order to encode the image, we will use the FileReader.readAsDataURL() method to securely read the contents of a file stored on the user’s computer and return a result attribute containing a data: URL representing the file’s data. We will use the addEventListener() method to attach a click event to the Upload button, which will trigger the encoding function: [see code snippet]

document.getElementById('button').addEventListener('click', function() {
var files = document.getElementById('file').files;
if (files.length > 0) {
getBase64(files[0]);
}
});
function getBase64(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
console.log(reader.result);
};
reader.onerror = function (error) {
console.log('Error: ', error);
};
}

The format of the returned result is the following:

data:[<mediatype>][;base64],<data>

The mediatype is a MIME type string, such as ‘image/jpeg’ for a JPEG image file and <data> is the Base64 encoded string representing the uploaded file.

Fetch the data to processing page

We now need to pass the data onto our second CloudPage for further processing. The reason why we are using two pages instead of one is that client-side JavaScript is run after the server-side is complete, which makes it impossible to run them in reverse order on the same page.

In order to pass the data between our CloudPages, we will use the fetch() method. But first, let’s prepare the data that we will need for our API call: [see code snippet]

var fileEncoded = reader.result;
var base64enc = fileEncoded.split(";base64,")[1];
var fullFileName = document.getElementById("file").files[0].name;
var fileName = fullFileName.split(".")[0];
var assetName = fullFileName.split(".")[1];
  • base64enc is the encoded string which represents the image
  • fullFileName is the full name of the file uploaded by the user (eg. “astro.png”)
  • fileName is the first part of the file name, before the filename extension (eg. “astro”)
  • assetName is the filename extension (eg. “png”)

Once we have the above, we can POST it in JSON format to the processing page that will contain the server-side script. Our request will look like this: [see code snippet]

fetch("https://pub.s10.exacttarget.com/xxxxxxx&quot;, { //provide URL of the processing page
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
base64enc: base64enc,
fileName: fileName,
assetName: assetName
})
})
.then(function(res) {
window.alert("Success!");
})
.catch(function(err) {
window.alert("Error!");
});
view raw fetch-data.js hosted with ❤ by GitHub

where the method is POST, headers specify that the content-type is JSON and in the JSON body we will have three attribute-value pairs, base64enc, fileName and assetName.

The fetch() method argument is the path to the CloudPage containing our server-side script, so, for now, you can create an empty CloudPage just to obtain the link.

The fetch() method returns a promise that resolves to a response to that request, whether it is successful or not: .then(function(res) / .catch(function(err).

The full client-side script

Let’s now put all of the above elements together to create our first CloudPage. The complete code will look like this: [see code snippet]

Please select a file to upload:
<br>
<input id="file" type="file" accept="image/*">
<br>
<button id="button">Upload</button>
<script runat="client">
document.getElementById("button")
.addEventListener("click", function() {
var files = document.getElementById("file").files;
if (files.length > 0) {
getBase64(files[0]);
}
});
function getBase64(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
//prepare data to pass to processing page
var fileEncoded = reader.result;
var base64enc = fileEncoded.split(";base64,")[1];
var fullFileName = document.getElementById("file").files[0].name;
var fileName = fullFileName.split(".")[0];
var assetName = fullFileName.split(".")[1];
fetch("https://pub.s10.exacttarget.com/xxxxxxxxx&quot;, { //provide URL of the processing page
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
base64enc: base64enc,
fileName: fileName,
assetName: assetName
})
})
.then(function(res) {
window.alert("Success!");
})
.catch(function(err) {
window.alert("Error!");
});
};
reader.onerror = function(error) {
console.log('Error: ', error);
};
}
</script>

Now we can move on and prepare the second part of the script.

Retrieve the posted data

In order to retrieve the data posted from our first CloudPage, we will use the getPostData() server-side JavaScript method, which sadly is not documented in the official Server-Side JavaScript Syntax Guide, but you can read about it in this post on Salesforce Stack Exchange. Once we access the body of our request, we can parse the data using the ParseJSON() function: [see code snippet]

//retrieve posted data
var jsonData = Platform.Request.GetPostData();
var obj = Platform.Function.ParseJSON(jsonData);

Prepare the data for API call

Now we can prepare the data for our API call. Let’s take a look at an example JSON body of a create asset request: [see code snippet]

POST /asset/v1/content/assets
{
"name": "Astro",
"assetType": {
"name": "png",
"id": 28
},
"file": "/9j/4AAQSkZJRgABAQAASABIAAD/2wBDAAQEBAQEBAcEBAcKBwcHCg0KCgoKDRANDQ0NDRAUEBAQEBAQFBQUFBQUFBQYGBgYGBgcHBwcHB8sDE94b5W7oHvIsTYPyGodpqA/ii4AwBAf+F//2Q=="
}

In the above, the first name represents the name of the file. The second parameter assetType, consists of a name which represents the file extension and an id of the asset type. Asset types are the file or content types supported by Content Builder and the full list can be found here: List of Asset Types.

Let’s prepare the data and match the asset type based on the name of the file extension: [see code snippet]

//prepare data for API call
var base64enc = obj.base64enc;
var fileName = obj.fileName;
var assetName = obj.assetName;
//match asset type with uploaded file (https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/base-asset-types.htm)
var assetTypes = { ai: 16, psd: 17, pdd: 18, eps: 19, gif: 20, jpe: 21, jpeg: 22, jpg: 23, jp2: 24, jpx: 25, pict: 26, pct: 27, png: 28, tif: 29, tiff: 30, tga: 31, bmp: 32, wmf: 33, vsd: 34, pnm: 35, pgm: 36, pbm: 37, ppm: 38, svg: 39, "3fr": 40, ari: 41, arw: 42, bay: 43, cap: 44, crw: 45, cr2: 46, dcr: 47, dcs: 48, dng: 49, drf: 50, eip: 51, erf: 52, fff: 53, iiq: 54, k25: 55, kdc: 56, mef: 57, mos: 58, mrw: 59, nef: 60, nrw: 61, orf: 62, pef: 63, ptx: 64, pxn: 65, raf: 66, raw: 67, rw2: 68, rwl: 69, rwz: 70, srf: 71, sr2: 72, srw: 73, x3f: 74, "3gp": 75, "3gpp": 76, "3g2": 77, "3gp2": 78, asf: 79, avi: 80, m2ts: 81, mts: 82, dif: 83, dv: 84, mkv: 85, mpg: 86, f4v: 87, flv: 88, mjpg: 89, mjpeg: 90, mxf: 91, mpeg: 92, mp4: 93, m4v: 94, mp4v: 95, mov: 96, swf: 97, wmv: 98, rm: 99, ogv: 100, indd: 101, indt: 102, incx: 103, wwcx: 104, doc: 105, docx: 106, dot: 107, dotx: 108, mdb: 109, mpp: 110, ics: 111, xls: 112, xlsx: 113, xlk: 114, xlsm: 115, xlt: 116, xltm: 117, csv: 118, tsv: 119, tab: 120, pps: 121, ppsx: 122, ppt: 123, pptx: 124, pot: 125, thmx: 126, pdf: 127, ps: 128, qxd: 129, rtf: 130, sxc: 131, sxi: 132, sxw: 133, odt: 134, ods: 135, ots: 136, odp: 137, otp: 138, epub: 139, dvi: 140, key: 141, keynote: 142, pez: 143, aac: 144, m4a: 145, au: 146, aif: 147, aiff: 148, aifc: 149, mp3: 150, wav: 151, wma: 152, midi: 153, oga: 154, ogg: 155, ra: 156, vox: 157, voc: 158, "7z": 159, arj: 160, bz2: 161, cab: 162, gz: 163, gzip: 164, iso: 165, lha: 166, sit: 167, tgz: 168, jar: 169, rar: 170, tar: 171, zip: 172, gpg: 173, htm: 174, html: 175, xhtml: 176, xht: 177, css: 178, less: 179, sass: 180, js: 181, json: 182, atom: 183, rss: 184, xml: 185, xsl: 186, xslt: 187, md: 188, markdown: 189, as: 190, fla: 191, eml: 192, text: 193, txt: 194, freeformblock: 195, textblock: 196, htmlblock: 197, textplusimageblock: 198, imageblock: 199, abtestblock: 200, dynamicblock: 201, stylingblock: 202, einsteincontentblock: 203, webpage: 205, webtemplate: 206, templatebasedemail: 207, htmlemail: 208, textonlyemail: 209, socialshareblock: 210, socialfollowblock: 211, buttonblock: 212, layoutblock: 213, defaulttemplate: 214, smartcaptureblock: 215, smartcaptureformfieldblock: 216, smartcapturesubmitoptionsblock: 217, slotpropertiesblock: 218, externalcontentblock: 219, codesnippetblock: 220, rssfeedblock: 221, formstylingblock: 222, referenceblock: 223, imagecarouselblock: 224, customblock: 225, liveimageblock: 226, livesettingblock: 227, contentmap: 228, jsonmessage: 230 };
var assetTypeID = assetTypes[assetName];

If you’re only going to upload images, you don’t need to include the whole list of asset types – the ones that start with “2” will be sufficient.

Create an asset using REST API

In order to make an API call using the /asset/v1/content/assets route, we first need to authenticate. You will need the Installed Package for API integration, the Client Id and Cient Secret. If you’re not sure how to authenticate programmatically in Salesforce Marketing Cloud, refer to my article, Salesforce Marketing Cloud API Authentication using Server-Side JavaScript.

As a security measure, it’s best to store your Client Id and Secret as encoded values in a Data Extension to avoid exposing them in your script. My preferred way is to use the EncryptSymmetric and DecryptSymmetric AMPscript functions for encryption, and a simple lookup to get their values from a Data Extension.

Here’s the code snippet for the authentication part:

<script runat="server">
Platform.Load("Core","1.1.1");
var authEndpoint = 'https://mcxxxxxxxxxxxxxxxxxxxxxxxxx.auth.marketingcloudapis.com&#39;;
var payload = {
client_id: "insert Client Id",
client_secret: "insert Client Secret",
grant_type: "client_credentials"
};
var url = authEndpoint + '/v2/token';
var contentType = 'application/json';
try {
var accessTokenRequest = HTTP.Post(url, contentType, Stringify(payload));
if(accessTokenRequest.StatusCode == 200) {
var tokenResponse = Platform.Function.ParseJSON(accessTokenRequest.Response[0]);
var accessToken = tokenResponse.access_token;
}
} catch (error) {
Write(Stringify(error));
}
</script>

Once we obtain the accessToken, we will need to include it in our final API call, along with the rest_instance_url: [see code snippet]

var headerNames = ["Authorization"];
var headerValues = ["Bearer " + accessToken];
var jsonBody = {
"name": fileName,
"assetType": {
"name": assetName,
"id": assetTypeID
},
"file": base64enc
};
var requestUrl = rest_instance_url + "asset/v1/content/assets"
var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);

The above call will create an image in Content Builder’s main folder and return the following response: [see code snippet]

{
"id":xxx,
"customerKey":"xxx",
"objectID":"xxx",
"assetType":{
"id":23,
"name":"jpg",
"displayName":"Image"
},
"fileProperties":{
"extension":"jpg",
"fileSize":24328,
"fileCreatedDate":"xxx",
"width":700,
"height":421,
"publishedURL":"xxxx"
},
"name":"astro",
"owner":{
"id":xxx,
"email":"",
"name":"APIuser",
"userId":"xxx"
},
"createdDate":"xxx",
"createdBy":{
"id":xxx,
"email":"",
"name":"APIuser",
"userId":"xxx"
},
"modifiedDate":"xxx",
"modifiedBy":{
"id":xxx,
"email":"",
"name":"APIuser",
"userId":"xxx"
},
"enterpriseId":xxx,
"memberId":xxx,
"status":{
"id":2,
"name":"Published"
},
"category":{
"id":xxx8,
"name":"Content Builder",
"parentId":0
},
"availableViews":[
],
"modelVersion":2
}

The full server-side script

Here is the full script for our second CloudPage: [see code snippet]

<script runat="server">
Platform.Load("Core","1");
try {
//fetch posted data
var jsonData = Platform.Request.GetPostData();
var obj = Platform.Function.ParseJSON(jsonData);
//prepare data for API call
var base64enc = obj.base64enc;
var fileName = obj.fileName;
var assetName = obj.assetName;
//match asset type with uploaded file (https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/base-asset-types.htm)
var assetTypes = { ai: 16, psd: 17, pdd: 18, eps: 19, gif: 20, jpe: 21, jpeg: 22, jpg: 23, jp2: 24, jpx: 25, pict: 26, pct: 27, png: 28, tif: 29, tiff: 30, tga: 31, bmp: 32, wmf: 33, vsd: 34, pnm: 35, pgm: 36, pbm: 37, ppm: 38, svg: 39, "3fr": 40, ari: 41, arw: 42, bay: 43, cap: 44, crw: 45, cr2: 46, dcr: 47, dcs: 48, dng: 49, drf: 50, eip: 51, erf: 52, fff: 53, iiq: 54, k25: 55, kdc: 56, mef: 57, mos: 58, mrw: 59, nef: 60, nrw: 61, orf: 62, pef: 63, ptx: 64, pxn: 65, raf: 66, raw: 67, rw2: 68, rwl: 69, rwz: 70, srf: 71, sr2: 72, srw: 73, x3f: 74, "3gp": 75, "3gpp": 76, "3g2": 77, "3gp2": 78, asf: 79, avi: 80, m2ts: 81, mts: 82, dif: 83, dv: 84, mkv: 85, mpg: 86, f4v: 87, flv: 88, mjpg: 89, mjpeg: 90, mxf: 91, mpeg: 92, mp4: 93, m4v: 94, mp4v: 95, mov: 96, swf: 97, wmv: 98, rm: 99, ogv: 100, indd: 101, indt: 102, incx: 103, wwcx: 104, doc: 105, docx: 106, dot: 107, dotx: 108, mdb: 109, mpp: 110, ics: 111, xls: 112, xlsx: 113, xlk: 114, xlsm: 115, xlt: 116, xltm: 117, csv: 118, tsv: 119, tab: 120, pps: 121, ppsx: 122, ppt: 123, pptx: 124, pot: 125, thmx: 126, pdf: 127, ps: 128, qxd: 129, rtf: 130, sxc: 131, sxi: 132, sxw: 133, odt: 134, ods: 135, ots: 136, odp: 137, otp: 138, epub: 139, dvi: 140, key: 141, keynote: 142, pez: 143, aac: 144, m4a: 145, au: 146, aif: 147, aiff: 148, aifc: 149, mp3: 150, wav: 151, wma: 152, midi: 153, oga: 154, ogg: 155, ra: 156, vox: 157, voc: 158, "7z": 159, arj: 160, bz2: 161, cab: 162, gz: 163, gzip: 164, iso: 165, lha: 166, sit: 167, tgz: 168, jar: 169, rar: 170, tar: 171, zip: 172, gpg: 173, htm: 174, html: 175, xhtml: 176, xht: 177, css: 178, less: 179, sass: 180, js: 181, json: 182, atom: 183, rss: 184, xml: 185, xsl: 186, xslt: 187, md: 188, markdown: 189, as: 190, fla: 191, eml: 192, text: 193, txt: 194, freeformblock: 195, textblock: 196, htmlblock: 197, textplusimageblock: 198, imageblock: 199, abtestblock: 200, dynamicblock: 201, stylingblock: 202, einsteincontentblock: 203, webpage: 205, webtemplate: 206, templatebasedemail: 207, htmlemail: 208, textonlyemail: 209, socialshareblock: 210, socialfollowblock: 211, buttonblock: 212, layoutblock: 213, defaulttemplate: 214, smartcaptureblock: 215, smartcaptureformfieldblock: 216, smartcapturesubmitoptionsblock: 217, slotpropertiesblock: 218, externalcontentblock: 219, codesnippetblock: 220, rssfeedblock: 221, formstylingblock: 222, referenceblock: 223, imagecarouselblock: 224, customblock: 225, liveimageblock: 226, livesettingblock: 227, contentmap: 228, jsonmessage: 230 };
var assetTypeID = assetTypes[assetName];
//authenticate to get access token
var authEndpoint = 'https://xxxxxxxx.auth.marketingcloudapis.com/&#39; //add authentication endpoint
var payload = {
client_id: "xxxxx", //pass Client ID
client_secret: "xxxxx", //pass Client Secret
grant_type: "client_credentials"
};
var url = authEndpoint + '/v2/token'
var contentType = 'application/json'
var accessTokenRequest = HTTP.Post(url, contentType, Stringify(payload));
if (accessTokenRequest.StatusCode == 200) {
var tokenResponse = Platform.Function.ParseJSON(accessTokenRequest.Response[0]);
var accessToken = tokenResponse.access_token
var rest_instance_url = tokenResponse.rest_instance_url
}
//make api call to create asset
if (base64enc != null) {
var headerNames = ["Authorization"];
var headerValues = ["Bearer " + accessToken];
var jsonBody = {
"name": fileName,
"assetType": {
"name": assetName,
"id": assetTypeID
},
"file": base64enc
};
var requestUrl = rest_instance_url + "asset/v1/content/assets"
var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);
}
} catch (error) {
Write("<br>error: " + Stringify(error));
}
</script>

You can now go back to your first CloudPage and start uploading!


Questions? Comments?

Leave a comment below or email me at zuzanna@sfmarketing.cloud.


47 thoughts on “Create a CloudPages form with an image/file upload option

  1. kevin

    Hi ,
    Thanks for your blog about it.
    could i ask why need to use cloud page to upload image,can you explain the background or the business case
    since we can use content builder to content builde

    Liked by 1 person

    1. Hi Kevin – sure!
      Think of all the use cases, where you create a form for your subscribers to fill in and there is a need to attach some sort of a file along with it.
      It could also be used in case you would want someone to be able to create/upload content, but without creating a user for them in Salesforce Marketing Cloud. Hope this helps!
      Zuzanna

      Liked by 2 people

  2. Hey zuzanne,
    I’ve been seeing a lot of these posts recently about how you can use cloud pages for various things and it scares me a bit to have cloud pages used without any additional authentication since it opens up your marketing cloud instance to security issues – maybe worth to mention this kind of stuff in future since a lot of people will just copy paste:)

    Like

  3. Evan Vega

    Hi Zuzanne –

    I have created a Cloud Page with a form to upload an image using your tutorial. I am able to successfully upload the image to Content Builder and also send a triggered email containing all of the form info using AMPScript, except I cannot for the life of me figure out how to grab the URL of the uploaded image in order to include it in the email. I see that the createAsset response returned by the API contains the created object along with it’s published URL and I learned how to assign it to a variable, but I still can’t figure out how to grab the value so I can upsert it to a DE with the other form data. I am using AMPScript to perform the upsert and it is working fine.

    Published URL:
    `var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);
    var asset = Platform.Function.ParseJSON(createAsset);
    var publishedUrl = asset.fileProperties.publishedURL;`

    Upsert:
    `Set @campaignMgr = RequestParameter(‘CampaignMgr’)
    Set @results_DE = “Results_DE”
    UpsertData(@results_DE,1,
    ‘CampaignMgr’, @campaignMgr)`

    Thanks in advance for your help!

    Like

    1. Hi Evan and thank you for your question – yes, you are correct, the url is returned in the response from the API call. You could do something like this in your server-side script to extract the url: parse the response from the call, then create a new variable containing the url, and write it to a Data Extensions. It would be very hard and inefficient to try to do this in AMPscript, so it’s better to go with SSJS:


      var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);
      //stringify response
      var respo = createAsset.Response.toString();
      //parse JSON
      var res = Platform.Function.ParseJSON(respo);
      //get the image url
      var imgURL = res.fileProperties.publishedURL;
      //initiate your data extension
      var myDE = DataExtension.Init("{{external key of the data extension}}");
      //add a new row with url to a data extensions
      myDE.Rows.Add({ URLcolumn: imgURL });

      Hope this helps 🙂

      Liked by 1 person

      1. Evan Vega

        Thank you for your quick response, Zuzanna! I have tried and tried and though the image uploads just fine, the publishedURL does not populate in the DE.

        I’m not sure if this is part of the issue, but the only way I can get the AMPScript upsert to work is by having the processing page URL as the action in my form. If it’s only in the fetch script, the page reloads and the image is uploaded to Content Builder, but I do not get the Success alert. If the processing page is the form action, I get directed to it upon submit and the image is uploaded and the AMPScript upsert happens, but the SSJS add does not work. (Once I get it working I’ll do all of the upserting with SSJS, but obviously I’m just not there yet!) 🙂

        I tried printing out the publishedURL, too, and it always comes up as undefined.

        var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);

        //stringify response
        var respo = createAsset.Response.toString();

        //parse JSON
        var res = Platform.Function.ParseJSON(respo);

        //get the image url
        var imgURL = res.fileProperties.publishedURL;

        //initiate your data extension
        var myDE = DataExtension.Init(“xxxx-xxx-xxxx-xxxx-xxxx”);

        //add a new row with url to a data extensions
        var fileURL = myDE.Rows.Add({FileURL:imgURL});

        Write(“File URL:” + fileURL);

        Like

  4. Evan Vega

    Ok, I’m an idiot – the DE I was attempting to write to had a primary key and of course, I was. not passing one through to it. All is good now! Thanks again for your help. 🙂

    Like

    1. saj

      Hey Evan, Zuzanna,

      I’m unable to output file name and URL using Write(“File URL:” + fileURL). I tried multiple things to display output on my processing page but no luck. I even tried SSJS inline declaration console.log(”); but still I am unable to display anything with Write() function. Can you please help?

      Like

  5. Hi Zuzanna,

    I’m getting a 400 error message when the content builder API call is made but haven’t been able to figure out yet why that is. This is the code I’m using on the processing page:

    Platform.Load(“Core”,”1″);
    var api = new Script.Util.WSProxy();
    api.setClientId({ “ID”: xx });

    try {
    //fetch posted data
    var jsonData = Platform.Request.GetPostData();
    var obj = Platform.Function.ParseJSON(jsonData);

    //prepare data for API call
    var base64enc = obj.base64enc;
    var fileName = obj.fileName;
    var assetName = obj.assetName;

    //match asset type with uploaded file (https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/base-asset-types.htm)
    var assetTypes = { ai: 16, psd: 17, pdd: 18, eps: 19, gif: 20, jpe: 21, jpeg: 22, jpg: 23, jp2: 24, jpx: 25, pict: 26, pct: 27, png: 28, tif: 29, tiff: 30, tga: 31, bmp: 32, wmf: 33, vsd: 34, pnm: 35, pgm: 36, pbm: 37, ppm: 38, svg: 39, “3fr”: 40, ari: 41, arw: 42, bay: 43, cap: 44, crw: 45, cr2: 46, dcr: 47, dcs: 48, dng: 49, drf: 50, eip: 51, erf: 52, fff: 53, iiq: 54, k25: 55, kdc: 56, mef: 57, mos: 58, mrw: 59, nef: 60, nrw: 61, orf: 62, pef: 63, ptx: 64, pxn: 65, raf: 66, raw: 67, rw2: 68, rwl: 69, rwz: 70, srf: 71, sr2: 72, srw: 73, x3f: 74, “3gp”: 75, “3gpp”: 76, “3g2”: 77, “3gp2”: 78, asf: 79, avi: 80, m2ts: 81, mts: 82, dif: 83, dv: 84, mkv: 85, mpg: 86, f4v: 87, flv: 88, mjpg: 89, mjpeg: 90, mxf: 91, mpeg: 92, mp4: 93, m4v: 94, mp4v: 95, mov: 96, swf: 97, wmv: 98, rm: 99, ogv: 100, indd: 101, indt: 102, incx: 103, wwcx: 104, doc: 105, docx: 106, dot: 107, dotx: 108, mdb: 109, mpp: 110, ics: 111, xls: 112, xlsx: 113, xlk: 114, xlsm: 115, xlt: 116, xltm: 117, csv: 118, tsv: 119, tab: 120, pps: 121, ppsx: 122, ppt: 123, pptx: 124, pot: 125, thmx: 126, pdf: 127, ps: 128, qxd: 129, rtf: 130, sxc: 131, sxi: 132, sxw: 133, odt: 134, ods: 135, ots: 136, odp: 137, otp: 138, epub: 139, dvi: 140, key: 141, keynote: 142, pez: 143, aac: 144, m4a: 145, au: 146, aif: 147, aiff: 148, aifc: 149, mp3: 150, wav: 151, wma: 152, midi: 153, oga: 154, ogg: 155, ra: 156, vox: 157, voc: 158, “7z”: 159, arj: 160, bz2: 161, cab: 162, gz: 163, gzip: 164, iso: 165, lha: 166, sit: 167, tgz: 168, jar: 169, rar: 170, tar: 171, zip: 172, gpg: 173, htm: 174, html: 175, xhtml: 176, xht: 177, css: 178, less: 179, sass: 180, js: 181, json: 182, atom: 183, rss: 184, xml: 185, xsl: 186, xslt: 187, md: 188, markdown: 189, as: 190, fla: 191, eml: 192, text: 193, txt: 194, freeformblock: 195, textblock: 196, htmlblock: 197, textplusimageblock: 198, imageblock: 199, abtestblock: 200, dynamicblock: 201, stylingblock: 202, einsteincontentblock: 203, webpage: 205, webtemplate: 206, templatebasedemail: 207, htmlemail: 208, textonlyemail: 209, socialshareblock: 210, socialfollowblock: 211, buttonblock: 212, layoutblock: 213, defaulttemplate: 214, smartcaptureblock: 215, smartcaptureformfieldblock: 216, smartcapturesubmitoptionsblock: 217, slotpropertiesblock: 218, externalcontentblock: 219, codesnippetblock: 220, rssfeedblock: 221, formstylingblock: 222, referenceblock: 223, imagecarouselblock: 224, customblock: 225, liveimageblock: 226, livesettingblock: 227, contentmap: 228, jsonmessage: 230 };

    var assetTypeID = assetTypes[assetName];

    //authenticate to get access token
    var authEndpoint = ‘xx.marketingcloudapis.com/’ //add authentication endpoint
    var payload = {
    client_id: “xx”, //pass Client ID
    client_secret: “xx”, //pass Client Secret
    grant_type: “client_credentials”
    };
    var url = authEndpoint + ‘/v2/token’;
    var contentType = ‘application/json’;

    var accessTokenRequest = HTTP.Post(url, contentType, Stringify(payload));
    if (accessTokenRequest.StatusCode == 200) {
    var tokenResponse = Platform.Function.ParseJSON(accessTokenRequest.Response[0]);
    var accessToken = tokenResponse.access_token
    var rest_instance_url = tokenResponse.rest_instance_url
    }
    //make api call to create asset
    if (base64enc != null) {
    var headerNames = [“Authorization”];
    var headerValues = [“Bearer ” + accessToken];
    var jsonBody = {
    “name”: fileName
    “assetType”: {
    “name”: assetName,
    “id”: assetTypeID
    },
    “file”: base64enc
    };

    var requestUrl = rest_instance_url + “asset/v1/content/assets”;

    var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);
    }

    } catch (error) {
    Write(“error: ” + Stringify(error));
    }

    Like

  6. This is the error message:

    {“message”:”An error occurred when attempting to evaluate a HTTPPost function call. See inner exception for details.”,”description”:”ExactTarget.OMM.FunctionExecutionException: An error occurred when attempting to evaluate a HTTPPost function call. See inner exception for details.
    Error Code: OMM_FUNC_EXEC_ERROR
    – from Jint –>

    — inner exception 1—

    System.Net.WebException: The remote server returned an error: (400) Bad Request. – from System

    “}

    Like

    1. Jesse F.

      Hello Macedoni22,

      Were you able to resolve the 400 Error? And if so, may I ask what you had done to figure this out?

      Thank you!

      Like

      1. Hi Jesse, the api error documentation wasn’t that helpful unfortunately and i was already aware of its existence. However, I did manage to solve the issue in the end by using postman which gives more detail when there is an error. It turned out i needed to set a few additional sharing parameters in the call.

        Like

  7. Jesse F.

    Hello Macedoni11,

    Interesting, JSON is not a language I typically program in — would you be able to share which parameters you added? I’ll look in to postman to see if I can figure it out as well.

    Thinking I may need to take a JSON course..

    Looking forward to hearing back from you,

    Thank you for the help and the response!

    Like

  8. Aviral

    I am facing this error on server side could page. Please help me out with this !

    error: {“message”:”Object expected: log”,”jintException”:”Jint.Native.JsException: Exception of type ‘Jint.Native.JsException’ was thrown.\r\n at Jint.ExecutionVisitor.Visit(MethodCall methodCall)\r\n at Jint.Expressions.MethodCall.Accept(IJintVisitor visitor)\r\n at Jint.ExecutionVisitor.Visit(MemberExpression expression)\r\n at Jint.Expressions.MemberExpression.Accept(IJintVisitor visitor)\r\n at Jint.ExecutionVisitor.Visit(ExpressionStatement statement)\r\n at Jint.Expressions.ExpressionStatement.Accept(IJintVisitor visitor)\r\n at Jint.ExecutionVisitor.Visit(BlockStatement statement)\r\n at Jint.Expressions.BlockStatement.Accept(IJintVisitor visitor)\r\n at Jint.ExecutionVisitor.Visit(TryStatement statement)”,”description”:”Jint.Native.JsException: Object expected: log\r\nException of type ‘Jint.Native.JsException’ was thrown. – from Jint\r\n\r\n”}

    Like

  9. Brendon

    Hey Zuzanna,

    Thanks so much for this article it has been super helpful. I am trying to add in some extra for inputs to this page and am getting stuck getting them to process. I have added Firstname for now, but am unable to get it to send to the processing page. I think it may be because of my event listener not picking up the extra field, but any help would be appreciated.

    This is the code so far

    Please select a file to upload:

    Name:

    Upload

    document.getElementById(“button”)
    .addEventListener(“click”, function() {
    var fname = document.getElementById(“firstname”);
    var files = document.getElementById(“file”).files;
    if (files.length > 0) {
    getBase64(files[0]);
    }
    });

    function getBase64(file) {
    var reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function() {

    //prepare data to pass to processing page
    var fileEncoded = reader.result;
    var base64enc = fileEncoded.split(“;base64,”)[1];
    var fullFileName = document.getElementById(“file”).files[0].name;
    var fileName = fullFileName.split(“.”)[0];
    var assetName = fullFileName.split(“.”)[1];

    fetch(“https://cloud.email.rea-group.com/advplusprocessing”, {
    method: “POST”,
    headers: {
    “Content-Type”: “application/json”
    },
    body: JSON.stringify({
    base64enc: base64enc,
    fileName: fileName,
    customerKey: “%%=v(@CKey)=%%”,
    assetName: assetName,
    fullFileName: fullFileName,
    ContactID: “%%=v(@ID)=%%”,
    FirstName: fname
    })
    })

    Like

  10. Thank you so much for the very useful information. I have one question: The upload works fine as long as the criteria are met (i.e. max. size of 5 MB). If the upload is to big, i.e. 6 MB, the upload does not work but the message on the side is “success”. Is there any way to tell the user here that it didn’t work?

    Thank you for your help!
    Best Jessica

    Like

  11. Sud

    Hi Zuzanna Jarczynska,

    Thanks for your article, I’m trying to update uploaded image URL in the DE. But, the response is showing as null. Please let me know your thoughts.

    var requestUrl = rest_instance_url + “asset/v1/content/assets”

    var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);

    }

    var cAsset = (Stringify(createAsset));
    var asset = Platform.Function.ParseJSON(cAsset);
    var publishedUrl = asset.fileProperties.publishedURL;

    Write(Stringify(imgURL)); Write(Stringify(createAsset));
    var rows = Platform.Function.InsertData(“Upload”,[“createAsset”,”publishedUrl”,”Date”],[cAsset,publishedUrl,Now()]);

    Like

  12. cheeran

    Hello, I am not able to print below variables, I am passing few more variables how to print them ??
    var jsonData = Platform.Request.GetPostData();
    var obj = Platform.Function.ParseJSON(jsonData);

    Like

  13. Ayush Thakkur

    HI Zuzanna,

    I have to build the same functionality for multiple file uplaod from single input file element. I have to pass the data to Sales cloud on click of submit button with other form elements.
    Any help around handling the files via Ampscript or SSJS and converting it to store it in Contentverison/Contentdocument object for Salesforce is appriciated.

    Kind Regards,
    Ayush

    Like

    1. This is a very complicated requirement and building this solution would also require understanding how Salesforce API works and which one to use. I suggest you sit down with the Salesforce Developer on your project and come up with a solution together.

      Like

  14. Rohit Sharma

    Hi Zuzanna,

    I would simply want to upload the multiple images into SFMC through this landing page. Can you please suggest and help the best code for the same?

    Thanks

    Like

  15. Sonal Gupta

    Hi Zuzanna Jarczynska,

    If i need to upload the image/file in a particular location in content builder then what changes should we need to do in code?

    Thanks

    Like

      1. Sonal Gupta

        Thanks a lot for this.
        And if i need to directly upload this image/file to Salescloud Files without storing the data in sfmc.

        Like

      2. Sonal Gupta

        I have read about content version api call for uploading in Salescloud but not getting clear picture how actual data flow will happen

        Like

  16. Praveen

    Hi Zuzanna Jarczynska,

    Its says error when I put input file tag inside the form tag but it works fine outside the form tag. Any reason

    Like

  17. Thiago Coutinho

    Hi Zuzanna, Thanks a lot for the great code and instructions, but i would like to know if it is possible to make it upload the image to a specific business unit in my marketing cloud, I wasn’t able to figure it out.

    Like

    1. That would be quite complex, hard to explain over a comment here.. you’d basically need some kind of a form field indicating a specific BU and then have a code resource in that BU to process the request from the master Cloud Page

      Like

      1. Thiago Coutinho

        Thanks for you quick reply, I think I expressed myself bad, I just want to to send the image to a business unit that I want it to be, not the client/person using the form. Is it still a complex thing?

        Like

      2. You need to put this code on a CloudPage in a Business Unit where you want to send the image. If you want the person who is uploading the image to be able to choose which BU it should upload to, then my previous comment is valid

        Like

      3. Thiago Coutinho

        I solved my problem by adding an account_id of my org inside the payload var, for anyone that had the same problem as me. Just creating it in the business unit wasn’t enough for it to upload in the right BU

        Like

  18. Jeshta

    Hi Zuzanna,

    In my cloud page form, some images gets upload but some are not. Not even getting any error. Same case with pdf files. May I know what causing this issue?

    Like

  19. Will

    For those having “Bad Request” error when resolving the Content Builder API call: the API call to create the Asset now requires the attribute “FileProperties.fileName” to be provided.
    So the payload should look like:

    {
    “name”: “image01”,
    “assetType”: {
    “name”: “png”,
    “id”: 28
    },
    “FileProperties”: {
    “fileName”: “image01.png”
    },
    “file”: “[Base64EncodedValue]”
    }

    Moreover, the “name” need to be unique, so you may want to add a timestamp or guid to it, if you are trying to upload the same image several time (ex: “name” = “image01-20230404_143427” )

    Finally for those who have trouble retrieving the “publishedURL”, the response needs to be transformed to read the properties.
    This can be achieved this way:


    // Call to create asset
    var createAsset = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);

    var publishedUrl = “-“;
    if(createAsset.StatusCode==200 || createAsset.StatusCode==201)
    {
    var objResp = Platform.Function.ParseJSON(createAsset.Response[0]);
    publishedUrl = objResp.fileProperties.publishedURL;
    }
    //Write(“publishedUrl=”+publishedUrl);

    Hope this helps.

    Like

  20. Nag

    Hi Zuzanna,

    The image is creating in email studio which is less then 3mb but if i try to upload 4mb image it is uploading but image is not creating in the email studio. Could you please help me out on this thing.

    Like

  21. Jithin James

    Thank you for your blog.

    I created a cloud page but getting blank data when data is submitted from IOS device. If I submit the data from PC or Android device it’s working. Could you please suggest a fix for this ?

    Thank you.

    Like

  22. Ishu

    Hi Zuzanna,

    I am able to upload multiple files from the cloud page form to salesforce using Content Document object however I see that max file I can upload is only less than 2MB & if it exceeds I am not even able to store the data in DE.

    Is there any limit that exists for file processing & upload in Cloud pages in SFMC?

    Like

Leave a comment