Last updated: February 4, 2025

<span aria-hidden="true" id="real-time-file-upload-tracking-in-php-with-session-upload-progress"></span>

# Real-time file upload tracking in PHP with session upload progress

![Tim Koschützki](/assets/images/teammates/avatar-tim-kos-1.jpg?dpl=dpl_C6YH6XrtnwbHLJcm1LDKywQn4CB3)

#### Tim Koschützki

Co-founder · Berlin, Germany · Show bio

[](https://x.com/tim%5Fkos)[](https://github.com/tim-kos)

Tracking file upload progress is crucial for providing a good user experience, especially when handling large files. PHP offers a built-in solution through its Session Upload Progress functionality, which provides reliable and efficient progress tracking without additional dependencies.

<span aria-hidden="true" id="server-configuration-requirements"></span>

## Server configuration requirements

Before implementing upload progress tracking, ensure your server is properly configured:

```ini
; php.ini configuration
session.upload_progress.enabled = On
session.upload_progress.cleanup = On
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"
upload_max_filesize = 10M
post_max_size = 10M

```

For Nginx, disable request buffering to ensure accurate progress tracking:

```nginx
location /upload {
    client_max_body_size 10M;
    client_body_buffer_size 128k;
    proxy_request_buffering off;
    # ... other configuration
}

```

<span aria-hidden="true" id="implementing-the-upload-handler"></span>

## Implementing the upload handler

Create a secure upload handler that processes files and implements proper validation:

```php
<?php
// upload.php
declare(strict_types=1);

session_start();

class FileUploadHandler {
    private const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
    private const MAX_FILE_SIZE = 10485760; // 10MB
    private const UPLOAD_DIR = __DIR__ . '/uploads';

    public function __construct() {
        if (!is_dir(self::UPLOAD_DIR)) {
            mkdir(self::UPLOAD_DIR, 0755, true);
        }
    }

    public function handleUpload(): array {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            return ['error' => 'Invalid request method'];
        }

        if (!isset($_FILES['file'])) {
            return ['error' => 'No file uploaded'];
        }

        $file = $_FILES['file'];

        try {
            $this->validateUpload($file);
            $filename = $this->generateSafeFilename($file['name']);
            $destination = self::UPLOAD_DIR . '/' . $filename;

            if (!move_uploaded_file($file['tmp_name'], $destination)) {
                throw new RuntimeException('Failed to move uploaded file');
            }

            return [
                'success' => true,
                'filename' => $filename,
                'size' => $file['size']
            ];
        } catch (Exception $e) {
            return ['error' => $e->getMessage()];
        }
    }

    private function validateUpload(array $file): void {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            throw new RuntimeException($this->getUploadErrorMessage($file['error']));
        }

        if ($file['size'] > self::MAX_FILE_SIZE) {
            throw new RuntimeException('File exceeds maximum size limit');
        }

        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->file($file['tmp_name']);

        if (!in_array($mimeType, self::ALLOWED_TYPES, true)) {
            throw new RuntimeException('Invalid file type');
        }
    }

    private function generateSafeFilename(string $filename): string {
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
        return bin2hex(random_bytes(16)) . '.' . $extension;
    }

    private function getUploadErrorMessage(int $error): string {
        return match($error) {
            UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize',
            UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE',
            UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
            UPLOAD_ERR_NO_FILE => 'No file was uploaded',
            UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
            UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
            UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload',
            default => 'Unknown upload error'
        };
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $handler = new FileUploadHandler();
    header('Content-Type: application/json');
    echo json_encode($handler->handleUpload());
    exit;
}
?>

```

<span aria-hidden="true" id="progress-tracking-implementation"></span>

## Progress tracking implementation

Create a progress tracking endpoint that safely retrieves upload progress:

```php
<?php
// progress.php
declare(strict_types=1);

session_start();

header('Content-Type: application/json');

$key = ini_get('session.upload_progress.prefix') . $_GET['id'] ?? '';
$progress = [];

if (isset($_SESSION[$key])) {
    $current = $_SESSION[$key];
    $progress = [
        'lengthComputable' => true,
        'loaded' => $current['bytes_processed'],
        'total' => $current['content_length'],
        'percentage' => ($current['bytes_processed'] / $current['content_length']) * 100
    ];
}

echo json_encode($progress);

```

<span aria-hidden="true" id="client-side-integration"></span>

## Client-side integration

Implement a responsive upload form with progress tracking:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>File Upload with Progress</title>
    <style>
      .progress {
        width: 100%;
        height: 20px;
        background: #f0f0f0;
        border-radius: 4px;
        overflow: hidden;
      }
      .progress-bar {
        width: 0;
        height: 100%;
        background: #4caf50;
        transition: width 0.3s ease;
      }
    </style>
  </head>
  <body>
    <form id="uploadForm" enctype="multipart/form-data">
      <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" id="progress-id" />
      <input type="file" name="file" required />
      <button type="submit">Upload</button>
      <div class="progress">
        <div class="progress-bar" id="progress-bar"></div>
      </div>
      <div id="status"></div>
    </form>

    <script>
      const form = document.getElementById('uploadForm')
      const progressBar = document.getElementById('progress-bar')
      const status = document.getElementById('status')
      const progressId = document.getElementById('progress-id')

      form.addEventListener('submit', async (e) => {
        e.preventDefault()

        // Generate unique ID for this upload
        const uploadId = Date.now().toString()
        progressId.value = uploadId

        const formData = new FormData(form)

        try {
          // Start progress tracking
          const tracker = trackProgress(uploadId)

          // Perform upload
          const response = await fetch('upload.php', {
            method: 'POST',
            body: formData,
          })

          // Stop progress tracking
          clearInterval(tracker)

          const result = await response.json()

          if (result.error) {
            throw new Error(result.error)
          }

          status.textContent = 'Upload complete!'
          progressBar.style.width = '100%'
        } catch (error) {
          status.textContent = `Error: ${error.message}`
          progressBar.style.backgroundColor = '#f44336'
        }
      })

      function trackProgress(uploadId) {
        return setInterval(async () => {
          try {
            const response = await fetch(`progress.php?id=${uploadId}`)
            const progress = await response.json()

            if (progress.lengthComputable) {
              const percentage = Math.round(progress.percentage)
              progressBar.style.width = `${percentage}%`
              status.textContent = `Uploading: ${percentage}%`
            }
          } catch (error) {
            console.error('Progress tracking error:', error)
          }
        }, 1000)
      }
    </script>
  </body>
</html>

```

<span aria-hidden="true" id="browser-compatibility"></span>

## Browser compatibility

This implementation works in all modern browsers that support:

* FormData API
* Fetch API
* CSS transitions
* ES6+ JavaScript features

For older browsers, consider adding polyfills or using a more traditional XMLHttpRequest approach.

<span aria-hidden="true" id="summary"></span>

## Summary

PHP's Session Upload Progress provides a reliable way to track file uploads without external dependencies. By combining it with proper security measures and error handling, you can create a robust upload system that provides real-time feedback to users.

For more advanced file upload capabilities, including chunked uploads and resume functionality, consider exploring the tus protocol implementation for PHP.

Happy coding!

\#php#handling-uploads-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed · 5 GB included in the free plan

Cancel anytime
