File download consists of front-end GUI and back-end code .

Caution: Never use include or require for uploaded files. Serve them as static or use file_get_contents(), readfile() etc.

Standard GUIAdvanced GUIBack-end
Standard Front-end Page

The Download form sends GET request to the back-end.

<!DOCTYPE html>
<meta charset="utf-8">
div.form {
    padding: 0.5rem 2rem 0; 
    background-color: #eee;
    line-height: 1.8rem;
    <div class="form">
        <div>File Download Form</div>
        <label>Download using link:</label><br />
        <a href="download_handler.php?download=flowers.jpg">flowers.jpg</a><br />
        <label>Download using javascript:</label><br />
        <button onclick="download(this)">flowers.jpg</button>
function download(btn){
    var fileName = btn.innerHTML;
    window.location = 'download_handler.php?download=' + fileName;
Standard GUI Demo

The Download form may send GET or POST request to the server. 

Click on button on link to download file
Advanced Front-end Page
<!DOCTYPE html>
<meta charset="utf-8">
.download-form {
    padding: 0.5rem 2rem 0.5rem; 
    background-color: #eee;
    line-height: 1.8rem;
.download-form-img {
    width: 80px;
    display: block;
    margin: 0 auto;
    cursor: pointer;
.download-form-floated {
    float: left;
    text-align: center;
.download-form-floated::after {
    content: '';
.download-form-file-list {
    margin-left: -0.5rem;
    <div class="download-form">
        <div style="font-weight:700;">File List Form</div>
        <label>Get files list then click to download:</label><br />
        <button onclick="get_list()">Get File List</button>
        <div class="download-form-file-list"></div>

function send_get_request(url, callback) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
    };"GET", url, true);

function get_list() {
    var file_list = document.getElementsByClassName('download-form-file-list')[0];
    file_list.innerHTML = '';
    send_get_request("list_handler.php?list=all", function(result){
        var myObj = JSON.parse(result.responseText);
        for(var i in myObj) {
            var file_name = myObj[i];

function add_file_btn(file_list,file_name) {
    var file_div = document.createElement("DIV");
    file_div.className = "download-form-floated";
    var file_img = document.createElement("IMG");
    file_img.className = "download-form-img";
    file_img.src = "list_handler.php?list=" + file_name;
    var file_cap = document.createElement("DIV");
    file_cap.innerHTML = file_name;
    file_img.onclick = function() {
        window.location = 'download_handler.php?download=' + file_name;

Advanced GUI Demo


code here

Download Handler

    $downloadDir = './downloads';
    require_once "FileDownloader.php";
    new FileDownloader($downloadDir);

List Handler

//query download directory
$downloadsPath = './downloads';
$thumbsDir = 'thumbs';

if (isset($_GET['list'])) {
    if ($_GET['list'] === 'all') {
        //get sorted file array and filter '..' and '.'
        $files = array_diff(scandir($downloadsPath), array('..', '.', $thumbsDir));

        //return filenames to front-end as json
        echo json_encode($files);
    } else {
        $fileName = filter_var($_GET['list'],FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        //replace non-image filenames with avatars
        if (substr($fileName,-4)=='.pdf') {
            $fileName = "pdf.png";
        $filePath = "$downloadsPath/$thumbsDir/$fileName";
        //return thumbs jpg or png 
        if (file_exists($filePath)) {
Download Class

class FileDownloader {
    function __construct($downloadDir = '.')
        if (isset($_GET['download'])) {
            $fileName = filter_var($_GET['download'],FILTER_SANITIZE_FULL_SPECIAL_CHARS);

    private function do_download($filePath)
    {   //check if file exists
        if (!file_exists($filePath)) {
            echo "File named <b>$filePath</b> does not exist."; 
        //get mime type from file name extension
        $fileExt = strtolower(substr(strrchr($filePath,"."),1)); 
        switch ($fileExt) {
            case "pdf": $mime="application/pdf"; break;
            case "exe": $mime="application/octet-stream"; break;
            case "zip": $mime="application/zip"; break;
            case "doc": $mime="application/msword"; break;
            case "xls": $mime="application/"; break;
            case "ppt": $mime="application/"; break;
            case "gif": $mime="image/gif"; break;
            case "png": $mime="image/png"; break;
            case "jpe": 
            case "jpeg":
            case "jpg": $mime="image/jpg"; break;
            default: $mime="application/force-download";
        //send headers and file contents
        header("Pragma: public");
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: private",false);
        header("Content-Type: $mime");
        header("Content-disposition: attachment; fileName=\"". basename($filePath) ."\""); 
        header("Set-Cookie: fileDownload=true; path=/ ");
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: " . @filesize($filePath));