42

Is it possible to upload a file using the Facebook Graph API using javascript, I feel like I'm close. I'm using the following JavaScript

var params = {};
params['message'] = 'PicRolled';
params['source'] = '@'+path;
params['access_token'] = access_token;
params['upload file'] = true;

function saveImage() {
    FB.api('/me/photos', 'post', params, function(response) {
        if (!response || response.error) {
            alert(response);
        } else {
            alert('Published to stream - you might want to delete it now!');
        }
    }); 
}

Upon running this I receive the following error...

"OAuthException" - "(#324) Requires upload file"

When I try and research this method all I can find out about is a php method that apears to solve this

$facebook->setFileUploadSupport(true);

However, I am using JavaScript, it looks like this method might be to do with Facebook Graph permissions, but I already have set the permissions user_photos and publish_stream, which I believed are the only ones I should need to perform this operation.

I have seen a couple of unanswered questions regarding this on stackoverflow, hopefully I can explained myself enough. Thanks guys.

Kara
  • 6,115
  • 16
  • 50
  • 57
Moz
  • 1,494
  • 6
  • 21
  • 32

13 Answers13

47

Yes, this is possible, i find 2 solutions how to do that and they are very similar to each other, u need just define url parameter to external image url

FIRST one using Javascript SDk:

var imgURL="http://farm4.staticflickr.com/3332/3451193407_b7f047f4b4_o.jpg";//change with your external photo url
FB.api('/album_id/photos', 'post', {
    message:'photo description',
    url:imgURL        
}, function(response){

    if (!response || response.error) {
        alert('Error occured');
    } else {
        alert('Post ID: ' + response.id);
    }

});

and SECOND one using jQuery Post request and FormData:

 var postMSG="Your message";
 var url='https://graph.facebook.com/albumID/photos?access_token='+accessToken+"&message="+postMSG;
 var imgURL="http://farm4.staticflickr.com/3332/3451193407_b7f047f4b4_o.jpg";//change with your external photo url
 var formData = new FormData();
 formData.append("url",imgURL);

  $.ajax({
                    url: url,
                    data: formData,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',

                    success: function(data){
                        alert("POST SUCCESSFUL");
                    }
                });
Volodymyr Dvornyk
  • 1,332
  • 1
  • 12
  • 15
  • I used the second one, it gives me : "$ is not defined" – Arrabi Nov 20 '11 at 09:44
  • 5
    ofcource, you need add jquery library;) – Volodymyr Dvornyk Nov 21 '11 at 10:15
  • 1
    First one is working perfectly, I don't know why the doc does not even specify url parameter. – Michael de Menten Mar 12 '12 at 12:17
  • 2
    has this method stoped working? I have been using the first method for some months and then suddenly failed (sends an invalid image message) and tried with the second one and gives me the same error message. – Oso Apr 15 '12 at 20:57
  • @Oso I have been getting 500 messages for a while, now it seems to be ok. Владимир Дворник, thank you a lot for referencing the url parameter, api doc does suck. – Fabiano Soriani May 15 '12 at 18:27
  • Владимир Дворник and @Fabiano PS: Now I know that Facebook is not allowing images that won't have a valid image file name URL (ending with .jpg or .png) – Oso May 17 '12 at 17:06
  • Does I need to keep the photo source at the server? – asdacap Jun 01 '12 at 16:14
  • @Владимир Дворник This seems to be perfectly fine when it comes to posting on "current user" wall. But can you tell how to post on friends wall!! – Surya Aug 24 '12 at 18:24
  • Its not working anymore.. can anybody please post a recent solution, which works? – Saurabh Jul 16 '14 at 18:34
  • This doesn't address loading a local file though which I think is what the original question was asking for. – Shaun314 Sep 06 '15 at 18:02
  • Is it possible to upload videos like this? I have tried but continuously get this error: "our video upload timed out before it could be completed. This is probably because of a slow network connection or because the video you\'re trying to upload is too large. Please try again." – peter Apr 24 '19 at 20:23
27

EDIT: this answer is (now) largely irrelevant. If your image is on the web, just specify the url param as per the API (and see examples in other answers). If you would like to POST the image content to facebook directly, you may want to read this answer to gain understanding. Also see HTML5's Canvas.toDataUrl().

The API says: "To publish a photo, issue a POST request with the photo file attachment as multipart/form-data."

FB is expecting that the bytes of the image to be uploaded are in the body of the HTTP request, but they're not there. Or to look at it another way - where in the FB.api() call are you supplying the actual contents of the image itself?

The FB.api() API is poorly documented, and doesn't supply an example of an HTTP POST which includes a body. One might infer from the absence of such an example that it doesn't support this.

That's probably OK - FB.api() is using something called XmlHttpRequest under the covers which does support including a body ... look it up in your favourite JavaScript reference.

However, you'll still have 2 sub-problems to solve:

  1. how to prepare the image bytes (and rest of the request) as multipart/form-data; and
  2. getting the bytes of the image itself

(incidentally, the need to encode the message body is probably what the PHP setFileUploadSupport(true) method is for - tell the facebook object to encode the message body as multipart/form-data before sending)

But it's a bit meessier than that

Unfortunately, sub-problem '2' may bite you - there is no way (last time I looked) to extract the bytes of an image from the browser-supplied Image object.

If the image to be uploaded is accessible via a URL, you could fetch the bytes with XmlHttpRequest. Not too bad.

If the image is coming from the user's desktop, your probable recourse is to offer the user a:

 <form enctype="multipart/form-data">
     <input type="filename" name="myfile.jpg" />
     <input type="hidden" name="source" value="@myfile.jpg"/>
     <input type="hidden" name="message" value="My Message"/>
     <input type="hidden" name="access_token" value="..."/> 
 </form> 

(notice that source references the name given to the file-upload widget)

... and hope that FB anticipated receiving the data in this manner (try it with a static HTML form first, before coding it up dynamically in JS). One might infer that in fact it would, since they don't offer another means of doing it.

David Bullock
  • 6,112
  • 3
  • 33
  • 43
  • Thanks, maybe I should use php it seems a lot more straight forward, but I will try this first! – Moz Feb 15 '11 at 08:30
  • bullock the path is accessible from a url, so do you think it would be a xml http request to supply the image data instead of the path and the rest of my code looks ok? I can't try till later tonight but thanks again – Moz Feb 15 '11 at 09:04
  • Ultimately, to use this api, you need to get the bytes of the image, then encode a request including those bytes as multipart/form-data, then send that as the body of a POST request to the facebook server. – David Bullock Feb 15 '11 at 11:26
  • If you've got the leisure of getting a server to upload the image, then I would probably do that anyhow. You'll have fewer headaches with the as-yet-non-standard binary capabilities of the XmlHttpRequest object in various browsers, plus you won't bang your head against the same-origin policy. The server is likely to be 'better connected' than the browser anyhow, and will get the job done quicker. – David Bullock Feb 15 '11 at 12:05
  • Hi David, would uploading in the body of a HTTP POST request work if the bytes come from canvas.toDataURL() (assuming we decode the base64)? – Crashalot Apr 18 '12 at 06:25
  • @Crashalot - probably. Read up on multipart/form-data, and contemplate a part with MIME headers like: "Content-Type: image/png" and "Content-Transfer-Encoding: base64". (That would save you from having to decode the base64! However, it depends on whether FaceBook's server will respect that or not. The API feels like it's designed for use with an HTML form, so if you had trouble, I'd try to supply a body that's equivalent to what a browser supplies (eg. sniff FireFox with the TamperData plugin). – David Bullock Apr 18 '12 at 14:32
  • hi, all why we using @ symbol at beginning of file source ? – alioygur May 25 '12 at 12:13
  • is it possible to share local image like url: 'image.png', – srini Mar 21 '13 at 11:45
17

i used @Владимир Дворник code with some modification, I had the same issue and with this code it worked very well:

        var imgURL = //your external photo url
        FB.api('/photos', 'post', {
            message: 'photo description',
            access_token: your accesstoken 
            url: imgURL
        }, function (response) {

            if (!response || response.error) {
                alert('Error occured:' + response);
            } else {
                alert('Post ID: ' + response.id);
            }

        });
Arrabi
  • 3,718
  • 4
  • 26
  • 38
11

Photos can be uploaded to facebook profile using Ajax as follows.

$.ajax({
            type: "POST",
            url: "https://graph.facebook.com/me/photos",
            data: {
                message: "Your Msg Goes Here",
                url: "http://www.knoje.com/images/photo.jpg[Replace with yours]",
                access_token: token,
                format: "json"
            },
            success: function(data){
               alert("POST SUCCESSFUL"); }
            });

So this is the best way to post photo to a facebook profile with GRAPH API and is the simple one.

In many answer i have seen that image url is shwon by the source,picture or image etc but that doesn't works.

The use of of source,picture or image leads to a (#324) Requires upload file error .

Best way to avoid the 324 error.

sumitkanoje
  • 1,217
  • 14
  • 22
  • what if you want to upload a local image? (i.e., the image is in your phone/computer and not hosted elsewhere) – ayjay Apr 11 '15 at 00:55
  • as per fb api you have to specify url of image which you want to post, u may try giving local path of image in case it works. – sumitkanoje Apr 15 '15 at 20:32
7

Only @Thiago's answer is answering the question of uploading data via javascript. I've found that the Facebook JS API doesn't cover this situation.

I've also brew & tested my personl solution.

Main steps

  1. Get the binary data of the image (I've used a canvas, but using an input box is possible as well)
  2. Form a multipart request with all necesarry data for the graph API call
  3. Include the binary data in the request
  4. Encode everything in a binary array and send it so via XHR

Code

Conversion utilities

var conversions = {
  stringToBinaryArray: function(string) {
    return Array.prototype.map.call(string, function(c) {
      return c.charCodeAt(0) & 0xff;
    });
  },
  base64ToString: function(b64String) {
    return atob(b64String);
  }
};

Image posting snippet

var DEFAULT_CALL_OPTS = {
  url: 'https://graph.facebook.com/me/photos',
  type: 'POST',
  cache: false,
  success: function(response) {
    console.log(response);
  },
  error: function() {
    console.error(arguments);
  },
  // we compose the data manually, thus
  processData: false,
  /**
   *  Override the default send method to send the data in binary form
   */
  xhr: function() {
    var xhr = $.ajaxSettings.xhr();
    xhr.send = function(string) {
      var bytes = conversions.stringToBinaryArray(string);
      XMLHttpRequest.prototype.send.call(this, new Uint8Array(bytes).buffer);
    };
    return xhr;
  }
};
/**
 * It composes the multipart POST data, according to HTTP standards
 */
var composeMultipartData = function(fields, boundary) {
  var data = '';
  $.each(fields, function(key, value) {
    data += '--' + boundary + '\r\n';

    if (value.dataString) { // file upload
      data += 'Content-Disposition: form-data; name=\'' + key + '\'; ' +
        'filename=\'' + value.name + '\'\r\n';
      data += 'Content-Type: ' + value.type + '\r\n\r\n';
      data += value.dataString + '\r\n';
    } else {
      data += 'Content-Disposition: form-data; name=\'' + key + '\';' +
        '\r\n\r\n';
      data += value + '\r\n';
    }
  });
  data += '--' + boundary + '--';
  return data;
};

/**
 * It sets the multipart form data & contentType
 */
var setupData = function(callObj, opts) {
  // custom separator for the data
  var boundary = 'Awesome field separator ' + Math.random();

  // set the data
  callObj.data = composeMultipartData(opts.fb, boundary);

  // .. and content type
  callObj.contentType = 'multipart/form-data; boundary=' + boundary;
};

// the "public" method to be used
var postImage = function(opts) {

  // create the callObject by combining the defaults with the received ones
  var callObj = $.extend({}, DEFAULT_CALL_OPTS, opts.call);

  // append the access token to the url
  callObj.url += '?access_token=' + opts.fb.accessToken;

  // set the data to be sent in the post (callObj.data = *Magic*)
  setupData(callObj, opts);

  // POST the whole thing to the defined FB url
  $.ajax(callObj);
};

Usage

postImage({
  fb: { // data to be sent to FB
    caption: caption,
    /* place any other API params you wish to send. Ex: place / tags etc.*/
    accessToken: 'ACCESS_TOKEN',
    file: {
      name: 'your-file-name.jpg',
      type: 'image/jpeg', // or png
      dataString: image // the string containing the binary data
    }
  },
  call: { // options of the $.ajax call
    url: 'https://graph.facebook.com/me/photos', // or replace *me* with albumid
    success: successCallbackFunction,
    error: errorCallbackFunction
  }
});

Extra

Extracting the binary string representation of a canvas image

var getImageToBeSentToFacebook = function() {
  // get the reference to the canvas
  var canvas = $('.some-canvas')[0];

  // extract its contents as a jpeg image
  var data = canvas.toDataURL('image/jpeg');

  // strip the base64 "header"
  data = data.replace(/^data:image\/(png|jpe?g);base64,/, '');

  // convert the base64 string to string containing the binary data
  return conversions.base64ToString(data);
}

Information on how to load the binaryString from an input[type=file]

HTML5 File API read as text and binary

Notes:

  1. There are of course alternative approaches as well
    • Using an HTML form in an iframe - you cannot get the response from the call
    • Using a FormData & File approach, but unfortunately in this case there are a lot of incompatilities which make the process harder to use, and you would end up duct-taping around the inconsistencies - thus my choice was manual data assembly since HTTP standards rarely change :)
  2. The solution does not require any special HTML5 features.
  3. The above example uses jQuery.ajax, jQuery.extend, jQuery.each
Community
  • 1
  • 1
Matyas
  • 13,473
  • 3
  • 60
  • 73
  • 1
    This was the only thing that worked for me to upload a file from the desktop and post it, thank you SO much. – Shaun314 Sep 06 '15 at 18:00
5

Yes, you can do this posting data to an iframe like here, or you can use jQuery File Upload . The problem is you can't get response from iframe, using plugin you can use a page handle.

Example: upload a video using jQuery File Upload

<form id="fileupload" action="https://graph-video.facebook.com/me/photos" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="acess_token" value="user_acess_token">
    <input type="text" name="title">
    <input type="text" name="description">
    <input type="file" name="file"> <!-- name must be file -->
</form>


<script type="text/javascript">

    $('#fileupload').fileupload({
        dataType: 'json',
        forceIframeTransport: true, //force use iframe or will no work
        autoUpload : true,
        //facebook book response will be send as param
        //you can use this page to save video (Graph Api) object on database
        redirect : 'http://pathToYourServer?%s' 
    });
</script>
Community
  • 1
  • 1
Guilherme Torres Castro
  • 15,135
  • 7
  • 59
  • 96
2

To upload a file from the local computer with just Javascript try HelloJS

<form onsubmit="upload();">
     <input type="file" name="file"/>
     <button type="submit">Submit</button>
</form>

<script>
function upload(){
   hello.api("facebook:/me/photos", 'post', document.getElementById('form'), function(r){
      alert(r&&!r.error?'Success':'Failed'); 
   });
}
</script>

There's an upload demo at http://adodson.com/hello.js/demos/upload.html

Drew
  • 1,420
  • 1
  • 11
  • 14
2

https://stackoverflow.com/a/16439233/68210 contains a solution that works if you need to upload the photo data itself and don't have a url.

Community
  • 1
  • 1
Daniel X Moore
  • 14,637
  • 17
  • 80
  • 92
2

This still works. I am using it as below:

var formdata= new FormData();
if (postAs === 'page'){
    postTo = pageId; //post to page using pageID
}

formdata.append("access_token", accessToken); //append page access token if to post as page, uAuth|paAuth
formdata.append("message", photoDescription);
formdata.append("url", 'http://images/image.png');

try {
    $.ajax({
        url: 'https://graph.facebook.com/'+ postTo +'/photos',
        type: "POST",
        data: formdata,
        processData: false,
        contentType: false,
        cache: false,
        error: function (shr, status, data) {
            console.log("error " + data + " Status " + shr.status);
        },
        complete: function () {
            console.log("Successfully uploaded photo to Facebook");
        }
    });
} catch (e) {
    console.log(e);
}

I have to ask though if you people have any idea if this is advisable or has a big security risk compared to using PHP api for Facebook.

Carmela
  • 687
  • 1
  • 8
  • 15
  • If someone needs it in the future, this code also works for uploading a file from a input[type=file]. –  Jul 21 '16 at 07:42
2

This works:

   function x(authToken, filename, mimeType, imageData, message) {
    // this is the multipart/form-data boundary we'll use
    var boundary = '----ThisIsTheBoundary1234567890';

    // let's encode our image file, which is contained in the var
    var formData = '--' + boundary + '\r\n';
    formData += 'Content-Disposition: form-data; name="source"; filename="' + filename + '"\r\n';
    formData += 'Content-Type: ' + mimeType + '\r\n\r\n';
    for (var i = 0; i < imageData.length; ++i) {
        formData += String.fromCharCode(imageData[i] & 0xff);
    }
    formData += '\r\n';
    formData += '--' + boundary + '\r\n';
    formData += 'Content-Disposition: form-data; name="message"\r\n\r\n';
    formData += message + '\r\n';
    formData += '--' + boundary + '--\r\n';

    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'https://graph.facebook.com/me/photos?access_token=' + authToken, true);
    xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);

    // Solving problem with sendAsBinary for chrome
    try {
        if (typeof XMLHttpRequest.prototype.sendAsBinary == 'undefined') {
            XMLHttpRequest.prototype.sendAsBinary = function(text) {
                var data = new ArrayBuffer(text.length);
                var ui8a = new Uint8Array(data, 0);
                for (var i = 0; i < text.length; i++) ui8a[i] = (text.charCodeAt(i) & 0xff);
                this.send(ui8a);
            }
        }
    } catch (e) {}
    xhr.sendAsBinary(formData);
};
isherwood
  • 58,414
  • 16
  • 114
  • 157
  • While this code may answer the question, it is better to include an explanation of what the code does, and why this is a better way of solving the problem than the other answers. – MattDMo Oct 20 '14 at 15:22
  • The answer is good, but it is dependent on the typed arrays, which is not overall supported. But actually this snippet was an inspiration to my [answer](http://stackoverflow.com/a/30150787/209427) – Matyas May 11 '15 at 08:33
1

I seem to have a similar problem, but solutions above didn't work.

I was using the solution suggested by Arrabi (just use the url property only) to post images without any problem. My images are around 2-3 MB each.

When I migrated my app to another server (changing the absolute url of my images in the post) the method kept giving me 324 errors for images above around 100k size.

I thought it was due to some Apache setting on my end, but when I changed apache for lighttpd the problem was still there.

The connections from Facebook actually show up in my (apache) log:

69.171.234.7 - - [08/Jun/2012:11:35:54 +0200] "GET /images/cards/1337701633_518192458.png HTTP/1.1" 200 2676608 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"

69.171.228.246 - - [08/Jun/2012:11:42:59 +0200] "GET /images/test5.jpg HTTP/1.1" 200 457402 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"

69.171.228.246 - - [08/Jun/2012:11:43:17 +0200] "GET /images/test4.jpg HTTP/1.1" 200 312069 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"

69.171.228.249 - - [08/Jun/2012:11:43:49 +0200] "GET /images/test2.png HTTP/1.1" 200 99538 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"

69.171.228.244 - - [08/Jun/2012:11:42:31 +0200] "GET /images/test6.png HTTP/1.1" 200 727722 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"

Only test2.png succeeded.

Community
  • 1
  • 1
Tom Lous
  • 2,819
  • 2
  • 25
  • 46
  • 1
    This problem was solved by increasing the speed to my server. It was first a 10Mbit uplink, now 100Mbit. Apparently the upload script has a timeout on the facebook side. – Tom Lous Jun 15 '12 at 04:55
0

I use the following to share a photo (some BitmapData from the Phaser framework). It seems to work...

// Turn data URI to a blob ready for upload.
dataURItoBlob(dataURI:string): Blob {
    var byteString = atob(dataURI.split(',')[1]);
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], { type: 'image/jpeg' });
}

// Share the given bitmapData as a photo on Facebook
sharePhoto(accessToken: string, photo: BitmapData, message: string): void {
    // Create form data, set up access_token, source and message
    var fd = new FormData();
    fd.append("access_token", accessToken);
    fd.append("source", this.dataURItoBlob(photo.canvas.toDataURL("image/jpeg")));
    fd.append("message", message);

    var request = new XMLHttpRequest();
    var thisPtr = this;
    request.onreadystatechange = function () {
        if (request.readyState == XMLHttpRequest.DONE) {
            var json = JSON.parse(request.responseText);
            if (json.hasOwnProperty("error")) {
                var error = json["error"];
                if (error.hasOwnProperty("type")) {
                    var errorType = error["type"];
                    if (errorType === "OAuthException") {
                        console.log("Need to request more permissions!");
                    }
                }
            }
        } else if (request.readyState == XMLHttpRequest.HEADERS_RECEIVED) {

        } else if (request.readyState == XMLHttpRequest.LOADING) {

        } else if (request.readyState == XMLHttpRequest.OPENED) {

        } else if (request.readyState == XMLHttpRequest.UNSENT) {

        }
    }
    request.open("POST", "https://graph.facebook.com/me/photos", true);
    request.send(fd);
}
Steven Craft
  • 1,243
  • 11
  • 15
0

In case anyone still looking for how to upload directly from canvas to Facebook photos, this works for me:

function postImageToFacebook(token, imageData, message, successCallback, errorCallback) {
  var fd = new FormData();
  fd.append("access_token", token);
  fd.append("source", imageData);
  fd.append("caption", message);

  $.ajax({
      url: "https://graph.facebook.com/me/photos?access_token=" + token,
      type: "POST",
      data: fd,
      processData: false,
      contentType: false,
      cache: false,
      success: function (data) {
        successCallback(data);
      },
      error: function (shr, status, data) {
        errorCallback(data);
      },
      complete: function (data) {
        console.log('Completed');
      }
  });
}

function dataURItoBlob(dataURI) {
  var byteString = atob(dataURI.split(',')[1]);
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);
  for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
  }
  return new Blob([ab], {type: 'image/jpeg'});
}

To use it

// *IMPORTANT*
var FBLoginScope = 'publish_actions'; // or sth like 'user_photos,publish_actions' if you also use other scopes.

var caption = "Hello Facebook!";
var successCallback = ...;
var errorCallback = ...;

var data = $('#your_canvas_id')[0].toDataURL("image/jpeg");
try {
  imageData = dataURItoBlob(data);
} catch (e) {
  console.log(e);
}

FB.getLoginStatus(function (response) {
  if (response.status === "connected") {
    postImageToFacebook(response.authResponse.accessToken, imageData, caption, successCallback, errorCallback);
  } else if (response.status === "not_authorized") {
    FB.login(function (response) {
        postImageToFacebook(response.authResponse.accessToken, imageData, caption, successCallback, errorCallback);
    }, {scope: FBLoginScope});
  } else {
    FB.login(function (response) {
        postImageToFacebook(response.authResponse.accessToken, imageData, caption, successCallback, errorCallback);
    }, {scope: FBLoginScope});
  }
});

Modified from: http://gorigins.com/posting-a-canvas-image-to-facebook-and-twitter/

aunnnn
  • 1,882
  • 2
  • 17
  • 23