Creating a Custom HTML Form for Ticket Submission Follow

Tip: All of the code from this lesson is available on GitHub @ .

If you are looking to add custom field support, read through this article, then check this fork on GitHub .


Many of our customers try to modify the Feedback Tab to their liking only to find that the tab itself is really a fixed entity. Many of its features are hardcoded and/or all-or-nothing. In this article we'll look at how to create your own HTML form that will create a new ticket.


  1. Create the form with only the necessary (bare minimum) fields for creating a new ticket.
  2. Make sure our setup grabs only the important fields and ignores other input fields in the same form.


  1. A server: You're going to need a place to host the finished product. It needs to have PHP installed. This is very basic and all web hosting providers have this included.
  2. Your Zendesk API key.

The Setup

  • We're going to create two documents: former.html and former.php.
  • The HTML file is going to contain four input boxes: Name, Email Address, Subject, and Description.
  • The PHP file is going to have our curlWrap function, though it will be modified, see here .

Let's Begin

Create 4 text boxes and make sure the name on each one matches the specifics in the table below:

Field Name (HTML) Zendesk Field What Goes Here?
z_requester requester email address
z_name name name
z_subject subject ticket subject
z_description description initial comment
Note: The field name is the name attribute of the HTML input element. Look below for an example where the name element is underlined .
<input type="text" name="z_name"  value="Your Name Here">

Why z_?

In later posts we will talk about more complex versions of this project and we will want to exclude some data from being automatically processed. The PHP file will be built to assume that any time it sees something submitted that has z_ in its name, that this is something that maps directly to a field of the same name (without the z_).

What if I want to map a custom field? If you have a custom field, you'll have to wait until version 2 coming soon!

In our sample HTML file we have the following:

Now that we have our HTML (easy part) let's create our PHP script. We'll start with a few define statements (A define statement is a way to set something throughout the entire script without worrying that the value will be changed):

define("ZDURL", "YOURZDURL");

Let's toss in our curlWrap but this time we need to make a few modifications.

function curlWrap($url, $json)

We want to include the json parameter (i.e., the information we are going to post) in our function. We didn't need it before because it was only for GETting information from the API. Now that we want to POST a ticket we need to include the information we are sending.

$ch = curl_init();
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt($ch, CURLOPT_MAXREDIRS, 10 );

These lines, respectively, are the same as before. They state: create a new curl object, follow any redirects in case the URL I'm passing forwards me somewhere else, and stop forwarding after a total of 10 redirects.

curl_setopt($ch, CURLOPT_URL, ZDURL.$url);
curl_setopt($ch, CURLOPT_USERPWD, ZDUSER."/token:".ZDAPIKEY);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");

These lines (above) are where things have changed the most. We are now using defined variables (instead of hard-coding) and we are POSTing data. The first line now uses defines to tell us what URL to use. The ZDURL is a defined value from the top of the script. The $url is a variable we passed in: /tickets.json. The next line has our username (ZDUSER) and the string "/token:" so we can use token-based authentication. This is followed by the token itself, as ZDAPIKEY.

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_setopt($ch, CURLOPT_USERAGENT, "MozillaXYZ/1.0");
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);

These three lines set the HTTPHEADER (telling the API we're sending JSON), set the useragent (what "browser we are using"), and lastly, set the POST data (the new ticket details).

Finally, we have the few remaining pieces of the puzzle before we put it all together:

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$output = curl_exec($ch);
$decoded = json_decode($output);
return $decoded;

The first line tells us we want the return details. In this case we don't really need it except to know if the ticket submission was a success or failure. The next line tells curl how long to wait before giving up. The final line is where all this work pays off, the EXECution. We execute the curl object we have been building and store the RETURNTRANSFER (ie: result) to the variable $output. We close our curl in the next line and decode it from JSON into a group of PHP objects and arrays (don't worry if this last bit doesn't make sense right now). The very last thing we do is return the result from our curl operation back to the main function.

So what does all this look like in one big chunk?

But we aren't done yet! In fact, we've only just started. We have the foundation but we certainly haven't finished the script. Right now the script wouldn't do anything. We need to call our curl function and actually pass information to it for anything to happen. Now for the core:

Here we have some real code to dissect. If you want, just paste this code in your document below the curlWrap and keep going. If you want a deeper understanding of what is going on you can click the link below.

Long Explanation (Can be skipped...)

The first part is a foreach loop. This does exactly what it says it does: it goes through a list and does something to each item on the list. What it does is enclosed between the "{" and the "}" brackets. In this case we start with the list $_POST which is actually comprised of all the content we POSTed from our HTML form. If we just "dumped" the contents of our $_POST array we would see something sort of like this: z_name = "Adam Panzer", z_subject="ticket subject i wrote" and so forth. The z_name is the key (identifier) and the "Adam Panzer" is the value. We tell PHP that $key is what we will refer to as the key, and $value is the value.

The next line is a basic if statement that says: if the key's name starts with "z_" then do something. That something is move the key and value to a new array and in the process "clean" the input of any code or HTML. Why? The simple answer is that you can put harmful code in a text box and if you don't "scrub it" you could lose all your data or expose your password. This is called an injection.

In the last line we compile all this together. We create arrays with our key value pairs. This might not make much sense until you know more about the structure of JSON. Look at this bit of JSON here:


At the top we see a curly bracket "{" which signifies the start of an object. Everything between the first { and the last } are all apart of one big object (Note: the last curl bracket "}" isn't shown in the above json, it's just a snippet). Beneath that is a part of the object, called a "child" object. In this case the "child" object is the "ticket" object. Inside we have key : value pairs. Each key is separated by a ":" and there is comma at the end to denote the end of the value portion and the start of a new key. In PHP we represent the objects as arrays. With all this in mind you can see, hopefully, that we pass in a set of array( key => value, key => value) sets to JSON_ENCODE. Each set block of array()'s statements is combined into the same object. In the above JSON snippet, the external_id and type are both in the same object. Thus they could be seen as this array( "external_id" => "null", "type" => "null" .... and so on. It's okay if this doesn't make complete sense just yet. With more practice (and possibly some Googling), you'll understand it.

Wrapping Up

The last line is how we execute the initial curlWrap function we discussed earlier. We set the variable $return equal to the result (the returned success or failure) of the curl command.

The last step is to tell PHP where to start and end everything. At the very top of the script, the very first characters need to be <?php and the last characters need to be ?> so that PHP knows the code starts at <?php and ends with ?>. Also important is to put a new line after the <?php and before the ?> as well.

The finished products of both former.php and former.html are attached to this article.

Final Steps

Before we call ourselves done we need to put both these files on our servers. Link them into your site however you like as long as they exist in the same directory/folder. Once that is done you can test it out by going to your site and submitting your first ticket.

Note that, as it stands right now, this script shows a blank page when the submission is complete. If you want to show a different page you can simply type in the HTML below the ?> (close tag) at the end of the document. DO NOT CHANGE THE FILE NAME OR THE EXTENSION. PHP will ignore the HTML code as it is not within/inside the <?php / ?> tags.

Next lesson

In the next lesson , I''ll show you how to place your ticket submission form into a popup.

Have more questions? Submit a request


  • 0

    I've used the exact code provided & attempted to run a test but I encounter the following error:

    Warning: json_encode() expects exactly 1 parameter, 2 given in /home/content/84/8573084/html/former.php on line 29

    The only tweaks I've made to the PHP file is to insert my API access, so I presume there is an issue with the default script? You can find the test page here:

    Any suggestions?

  • 0

    Hi George,

    You'll need to make sure you are running PHP 5.3.0 at least in order to use json_encode in the way I instructed above. The most current stable version of php is 5.4.4 and the (old) stable version was 5.3.3. 


    Check your php version and let me know the version.





  • 0

    Hi Adam,

    The server was running v5.2! It's now been updated to v5.3.13 what appears to be the most recent available for me.

    Re-submitting the test form no longer displays an error, but it doesn't submit a ticket either! It merely displays the php script.

    Please can you advise how I can go about getting this sorted.



  • 0

    It's working for me. I went to your link and got a ticket submission email. Are you sure it's not working?

  • 0

    Well would you look at that... it's working fine!! I can only guess that parts of the update hadn't completed!

    Thank you for your help Adam!

  • 0

    Thanks for the code - it works well.


    One question though, is there a way to allow people to attach file(s) to tickets created using this form?

  • 0

    I'm curious about the file attachment question that Dennis asked too, and I'm also wondering if it's now possible to add custom fields?

  • 0

    @Noah, Dennis: Sorry about the lack of follow-up on that last question. Posting a file is trickier via Zendesk because we have a different end-point for attachments. 


    In regards to this article I haven't covered attachments as it does fall outside the scope of "simple". In regards to how to do this, I need to think about if there should be an extra part to this tutorial. I would say the easiest way to include attachments is to create a trigger in your Zendesk that goes as follows:



    ticket is created

    ticket channel is web service (api)



    email requester


    The email that goes to the requester can say something along the lines of "If you would like to attach a file to your request please reply to this message and attach any and all files."


    This is the simplest way for you to implement file attachments without a lot of custom coding. I do understand that this is not the simplest solution for your customers.


    Custom fields also fall into this category of being beyond the scope of "simple". The reason for this is because it requires extra calls to the API to make sure that we validate user input correctly. That being said, it would absolutely be possible to integrate only specific types of custom fields into this project (ie: drop down only). I'll look into adding an addendum to this article in the coming weeks if I can find the time. 

  • 0


    No problem - I understand it's more complex :)

    If you can come up with a solution of integrating some magic APIs that'll let us integrate attachments into HTML forms, that would be great. Approximately 80% of my support requires attachments (logs), so it would greatly help those submitting via the web, as they wouldn't have to wait for me to get back to them. I can't really automate it my case, since not every request needs this.

    I'm happy to get my hands dirty in coding on an advanced level to get this working ;)


  • 0

    Hey, I really need to know how to work with custom fields with this code! Give us some hints please!

  • 0

    I just want to give everyone a heads up. I'm working on the custom field request that Oscar posted. It should go out soon (hopefully by the end of the week). 

  • 0

    Thanks Adam!  I'm excited to see the custom fields instructions!


    On another note, I'd like to incorporate the phone number field on my form.  I've added z_phone to the js and the form, but that didn't work.  Any thoughts would be much appreciated.  Thanks!

  • 0

    Hi Noah,

    The phone number isn't apart of the ticket object. It's a user object. In other words, the phone number isn't something that is part of a ticket but rather it's part of the requester itself. 

    I'll have the custom fields code release in an hour or so (hopefully!)

  • 0

    @Noah, give this a try: and let me know if you run into issues. 

    caveat emptor: note that when using this script, end-users could modify the DOM to add additional custom fields that might not otherwise be settable by the end-user. understand that the only limitation would be knowing the custom field id number of the other custom fields in your Zendesk.

  • 0

    Awesome.  I'll play with it and see what I can do.  Thanks!

  • 0

    Forgot one last addition: there is also a new define statement for custom fields. updated the config.example.php file so look in there too!

  • 0

    Is there a way to submit ticket with this form not using admin email? Right now this form creates ticket with an admin email address "on behalf of" requester. I was looking for the solution that will remove the "on behalf of" part and use only the requester info. Is it possible?

  • 0

    Hi Rafal,

    I'm not sure what the benefit of that would be and the risks/issues with using the current version of x-on-behalf-of with this script would leave too many vulnerabilities open. 

  • 0

    Hi Adam,

    I just wanted to remove on-behalf-of part from the ticked view in the console.

    Right now it suggest that the admin created the ticket manually in zendesk console which is not true. The ticket was submitted via web form by the requester so the ticket view shouldn't show on-behalf-of. 

  • 0

    Could you help me, please.

    I've installed this form to our site, all works perfectly, but when I try to add some fields, like 'type', 'group_id' or else - there is no changes in the ticket. How can additional fields can be added to ticket?

    Maybe I'm using a wrong syntax?

    For example I've created the hidden field in the form with name 'z_type' and value="problem",

    in the former.php I've added it to array:

    $create = json_encode(array('ticket' => array('subject' => $arr['z_subject'],** 'type' => $arr['z_type']**, 'comment' => array( "value"=> $arr['z_description']), 'requester' => array('name' => $arr['z_name'], 'email' => $arr['z_requester']))));


    What I am doing wrong?

  • 0

    Also I've tryed to add custom field, created in Zendesk, added to form:


    <td valign="top" align="right" height="40px">Text:</td><td valign="top"><input type="text" value="" name="с\_IDNUMBER"></td>



    But zero result...

  • 0

    @pavel I haven't used this in a few months, but at the time of my coding Custom Field Inputs were not available. As per the documentation it was something that was in development for version 2.

    I saw that Adam mentioned this new release was due for release, but personally I've not seen any reference to it.

    This may not be an ideal solution, but you could use JavaScript to combine your custom values to be passed as part of the description. I wrote a very basic jQuery function, hope this helps:


    function combineValues() {

      var fields = $('.zinput').serializeArray();

      var values = ""

      jQuery.each(fields, function(i, field){

        values += ": " + field.value + "\n";





    Disclaimer: I am by no mean an expert when it comes to JS so the above may be a little buggy, but it suites my needs. 

  • 0

    Thank you very much, George.

    It's a pity to know, that the feature doesn't work correctly...

    I'll try to email for Support team, maybe they could help some way more.

  • 0

    Hey folks: 

    Sorry about the response delay. I'll loop in the right people to get some answers here. 

  • 0

    @Justin: Thanks! I'll take it from here:


    @Pavel and George:


    give this a try: and let me know if you run into issues. 

    caveat emptor: note that when using this script, end-users could modify the DOM to add additional custom fields that might not otherwise be settable by the end-user. understand that the only limitation would be knowing the custom field id number of the other custom fields in your Zendesk.

  • 0

    @Adam, thanks for that! Really good timing as I'm just updating the page where my scripts live.

    I think it's appropriate to say, winner, winner, chicken dinner!

  • 0

    Hello, i'm trying to use this tutorial to build a custom form with custom fields, but on submit i get the following error 

    CURLOPT_FOLLOWLOCATION cannot be activated when safe_mode is enabled or an open_basedir

    What should i do? Should i contact my host ?

  • 0

    Also, i tried the form on another server i don't get any error but it doesn't submit anything :(

  • 0

    Hi ThemesHub


    In regards to your first issue, take a look at this StackOverflow doc:


    In general: can you open a new ticket with a copy of your phpinfo settings?

  • 0

    @Adam, I've come back around too look at this & I have noticed the github repository currently has the documentation as "pending internal review". Please can you advise when this will be live so that I can ensure future forms conform! 


Powered by Zendesk