117

When I use curl via POST and set CURLOPT_POSTFIELD do I have to urlencode or any special format?

for example: If I want to post 2 fields, first and last:

first=John&last=Smith

what is the exact code/format that should be used with curl?

$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$reply=curl_exec($ch);
curl_close($ch);
Mahmoud Gamal
  • 78,257
  • 17
  • 139
  • 164
Ken
  • 2,849
  • 8
  • 24
  • 23

10 Answers10

107

In case you are sending a string, urlencode() it. Otherwise if array, it should be key=>value paired and the Content-type header is automatically set to multipart/form-data.

Also, you don't have to create extra functions to build the query for your arrays, you already have that:

$query = http_build_query($data, '', '&');
kodeart
  • 1,903
  • 1
  • 19
  • 27
  • 16
    You can just use `http_build_query($data)` since `&` is the default separator. – nullability Apr 14 '14 at 17:59
  • 8
    Of course you can skip them. This is to illustrate the other two arguments that you may (or may not) change (ex. the default separator) to suit your specific needs. – kodeart Apr 17 '14 at 19:03
70

EDIT: From php5 upwards, usage of http_build_query is recommended:

string http_build_query ( mixed $query_data [, string $numeric_prefix [, 
                          string $arg_separator [, int $enc_type = PHP_QUERY_RFC1738 ]]] )

Simple example from the manual:

<?php
$data = array('foo'=>'bar',
              'baz'=>'boom',
              'cow'=>'milk',
              'php'=>'hypertext processor');

echo http_build_query($data) . "\n";

/* output:
foo=bar&baz=boom&cow=milk&php=hypertext+processor
*/

?>

before php5:

From the manual:

CURLOPT_POSTFIELDS

The full data to post in a HTTP "POST" operation. To post a file, prepend a filename with @ and use the full path. The filetype can be explicitly specified by following the filename with the type in the format ';type=mimetype'. This parameter can either be passed as a urlencoded string like 'para1=val1&para2=val2&...' or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data. As of PHP 5.2.0, files thats passed to this option with the @ prefix must be in array form to work.

So something like this should work perfectly (with parameters passed in a associative array):

function preparePostFields($array) {
  $params = array();

  foreach ($array as $key => $value) {
    $params[] = $key . '=' . urlencode($value);
  }

  return implode('&', $params);
}
Czechnology
  • 14,832
  • 10
  • 62
  • 88
  • 12
    Why would you pass a string if you can pass an array...? – ThiefMaster Mar 07 '11 at 20:54
  • You're right, I'm just somehow used to this because it's the same as GET - a matter of habit so that you won't "forget" to escape next time you're doing GET. – Czechnology Mar 07 '11 at 21:42
  • 1
    I think $key should be encoded too, just in case you have it like "name&surname" etc. Especially if data is given by the end user – Marius Balčytis Jun 07 '12 at 13:28
  • 2
    @barius, I agree with you. And I think http_build_query() actually is better than the function defined above. – skyfree Jul 19 '14 at 03:15
  • 1
    @skyfree I agree! That function was added in php5 though which was still far from standard in 2011. – Czechnology Jul 19 '14 at 06:55
  • 1
    why http_build_query is required when you could just pass array? – Offenso Oct 14 '17 at 00:46
  • @Offenso - without `http_build_query`, what is sent, for an array, is a *multi-part form*. This may or may not be what you want, but it requires different code at the receiving end. See [curl examples in PHP manual](https://www.php.net/manual/en/curl.examples-basic.php#117009) -- Roberto Braga's example "when using curl to post form data and you use an array for CURLOPT_POSTFIELDS option, the post will be in multipart format". Wrapping array with `http_build_query` instead sends the traditional `key1=value1&key2=value2`. – ToolmakerSteve Apr 10 '19 at 09:58
42

Do not pass a string at all!

You can pass an array and let php/curl do the dirty work of encoding etc.

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • 19
    passing an array will be a different Content-type than a string, so there is a good reason to do so. Took me a while to figure that out. – Thomas Vander Stichele Nov 30 '12 at 19:12
  • I have no idea why, but I find that on my win dev pc passing an array takes about a second longer (1.029s using array vs. 0.016s using `http_build_query()` on that same array) – kratenko Jan 27 '15 at 10:44
  • 3
    Nested arrays won't work. cURL will try to convert them to string and a PHP _Array to string conversion_ notice will follow – 7ochem Jul 05 '18 at 09:07
  • This is a **dangerous** answer in many situations where it isn't just a basic key-based, single dimension array. – Theodore R. Smith Jul 01 '22 at 03:54
15

It depends on the content-type

url-encoded or multipart/form-data

To send data the standard way, as a browser would with a form, just pass an associative array. As stated by PHP's manual:

This parameter can either be passed as a urlencoded string like 'para1=val1&para2=val2&...' or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data.

JSON encoding

Neverthless, when communicating with JSON APIs, content must be JSON encoded for the API to understand our POST data.

In such cases, content must be explicitely encoded as JSON :

CURLOPT_POSTFIELDS => json_encode(['param1' => $param1, 'param2' => $param2]),

When communicating in JSON, we also usually set accept and content-type headers accordingly:

CURLOPT_HTTPHEADER => [
    'accept: application/json',
    'content-type: application/json'
]
Buzut
  • 4,875
  • 4
  • 47
  • 54
9

For CURLOPT_POSTFIELDS, the parameters can either be passed as a urlencoded string like para1=val1&para2=val2&.. or as an array with the field name as key and field data as value

Try the following format :

$data = json_encode(array(
"first"  => "John",
"last" => "Smith"
));

$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$output = curl_exec($ch);
curl_close($ch);
René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • 4
    Hi Shraddha, `json_encode()` will give you something really different of a valid parameter string as `first=John&last=Smith`. `json_encode()` will output: `{"first":"John","last":"Smith"}` which will then become the raw body of your POST request. – 7ochem Jul 05 '18 at 08:55
  • 1
    To add to @7ochem's comment: Unless the recipient is expecting *a single parameter containing a json-encoded string*, instead of `json_encode(...)` do `http_build_query(...)`. This will create the expected "url-encoded string" containing the parameters separated by "&". – ToolmakerSteve Apr 10 '19 at 09:54
9

One other major difference that is not yet mentioned here is that CURLOPT_POSTFIELDS can't handle nested arrays.

If we take the nested array ['a' => 1, 'b' => [2, 3, 4]] then this should be be parameterized as a=1&b[]=2&b[]=3&b[]=4 (the [ and ] will be/should be URL encoded). This will be converted back automatically into a nested array on the other end (assuming here the other end is also PHP).

This will work:

var_dump(http_build_query(['a' => 1, 'b' => [2, 3, 4]]));
// output: string(36) "a=1&b%5B0%5D=2&b%5B1%5D=3&b%5B2%5D=4"

This won't work:

curl_setopt($ch, CURLOPT_POSTFIELDS, ['a' => 1, 'b' => [2, 3, 4]]);

This will give you a notice. Code execution will continue and your endpoint will receive parameter b as string "Array":

PHP Notice: Array to string conversion in ... on line ...

7ochem
  • 2,183
  • 1
  • 34
  • 42
6

According to the PHP manual, data passed to cURL as a string should be URLencoded. See the page for curl_setopt() and search for CURLOPT_POSTFIELDS.

Surreal Dreams
  • 26,055
  • 3
  • 46
  • 61
2

We were looking for the same solution when we wrote a test for CMS Effcore. The solution turned out to be quite simple and it is shown below:

$data = [
  'name[0]' => 'value 1',
  'name[1]' => 'value 2',
  'name[2]' => 'value 3',
  'id'      => 'value 4'
];

$data = array(
  'name[0]' => 'value 1',
  'name[1]' => 'value 2',
  'name[2]' => 'value 3',
  'id'      => 'value 4'
);
2

Interestingly the way Postman does POST is a complete GET operation with these 2 additional options:

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, '');

Just another way, and it works very well.

Klompenrunner
  • 723
  • 7
  • 13
-3

This answer took me forever to find as well. I discovered that all you have to do is concatenate the URL ('?' after the file name and extension) with the URL-encoded query string. It doesn't even look like you have to set the POST cURL options. See the fake example below:

//create URL
$exampleURL = 'http://www.example.com/example.php?';

// create curl resource
$ch = curl_init(); 

// build URL-encoded query string
$data = http_build_query(
    array('first' => 'John', 'last' => 'Smith', '&'); // set url
curl_setopt($ch, CURLOPT_URL, $exampleURL . $data); 

// return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 

// $output contains the output string
$output = curl_exec($ch); 

// close curl resource to free up system resources <br/>
curl_close($ch);

You can also use file_get_contents():

// read entire webpage file into a string
$output = file_get_contents($exampleURL . $data);
Marco Leogrande
  • 8,050
  • 4
  • 30
  • 48