What is AWS S3 and Why Integrate It with CodeIgniter 4?
Amazon Simple Storage Service (S3) is a highly durable, scalable object storage service used by millions of websites. Benefits of CodeIgniter 4 + AWS S3 integration include:
- Unlimited scalable storage without server disk limits
- 99.999999999% (11 9's) durability
- Built-in security features (encryption, IAM policies)
- Lower costs for large files vs local hosting
- Easy CDN integration (CloudFront) for fast global delivery
Local file storage becomes a bottleneck as your app grows – S3 solves this permanently.

Table Of Content
1 Prerequisites
- PHP 8.2+
- Composer
- Active AWS account with S3 bucket and IAM user (Access Key + Secret)
2 How to Set Up AWS S3 File Upload in CodeIgniter 4 (Step-by-Step)
3 Create / Install a Codeigniter 4 Project
3.1 Install Codeigniter 4 Project
Use the following command to install new Codeigniter Project.
composer create-project codeigniter4/appstarter ci-4-amazon-s3-app
Then, navigate to your project directory:
cd ci-4-amazon-s3-app
3.2 Configure Environment (.env)
Also we can do this in command Line.
Open project in Command Line
sudo cp env .env
Above command will create a copy of env file to .env file. Now we are ready to set environment variables.
Configure Development Mode
CodeIgniter starts up in production mode by default. You need to make it in development mode to debug or check any error if you are working with application.
Open .env file from root.
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
Now application is in development mode.
4 Create / Login into AWS Account
After Sign into the account, In the top bar and Search for the S3 service and go to the S3 Management Console.
In S3 Management Console create a New Bucket to store files.
Get AWS Access Key ID and AWS Secret Access Key:
Search "IAM Console" and go to the IAM Console.
From the left navigation menu, select Users under the Access management section.
Create a user with AmazonS3FullAccess permission.
Once User Created Succefully, the Access Key ID and Secret Access Key will be generated.
5 Install AWS SDK for PHP
Use the following command to install PHP AWS SDK.
composer require aws/aws-sdk-php
6 Configure AWS Credentials in .env file
Add these to your .env file (replace with your actual values):
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_REGION=your-region # e.g., us-east-1
AWS_BUCKET=your-bucket-name
# Optional: AWS_ENDPOINT=https://s3.amazonaws.com (for custom S3-compatible services)
7 Create S3 Service Class
<?php
namespace App\Services;
use Aws\S3\S3Client;
use Aws\Exception\AwsException;
class S3Service
{
protected $s3;
protected $bucket;
public function __construct()
{
$this->s3 = new S3Client([
'region' => getenv('AWS_REGION'),
'version' => 'latest',
'credentials' => [
'key' => getenv('AWS_ACCESS_KEY_ID'),
'secret' => getenv('AWS_SECRET_ACCESS_KEY'),
],
// Uncomment if using a custom endpoint
// 'endpoint' => getenv('AWS_ENDPOINT'),
]);
$this->bucket = getenv('AWS_BUCKET');
}
public function uploadFile($filePath, $key)
{
try {
$result = $this->s3->putObject([
'Bucket' => $this->bucket,
'Key' => $key,
'SourceFile' => $filePath,
'ACL' => 'private', // Change to 'public-read' if files should be public
]);
return $result->get('ObjectURL');
} catch (AwsException $e) {
return $e->getMessage();
}
}
public function getFileUrl($key)
{
return $this->s3->getObjectUrl($this->bucket, $key);
}
public function listFiles()
{
$objects = $this->s3->getIterator('ListObjects', [
'Bucket' => $this->bucket,
'Prefix' => ''
]);
return $objects;
}
public function deleteFile($key)
{
try {
$this->s3->deleteObject([
'Bucket' => $this->bucket,
'Key' => $key,
]);
return true;
} catch (AwsException $e) {
return $e->getMessage();
}
}
}
?>
8 Create the Controller
Use the following artisan command to Create Controller.
php spark make:controller S3Controller
app/Controllers/S3Controller.php
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Services\S3Service;
class S3Controller extends BaseController
{
public function index()
{
return view('upload_form');
}
public function upload()
{
$file = $this->request->getFile('file');
if ($file->isValid() && !$file->hasMoved()) {
$filePath = $file->getTempName();
$fileName = $file->getName();
$s3Service = new S3Service();
$fileUrl = $s3Service->uploadFile($filePath, $fileName);
if (strpos($fileUrl, 'http') === 0) { // Check if upload succeeded (returns URL on success)
return redirect()->to('list')->with('message', 'File Uploaded Successfully!');
} else {
return redirect()->to('list')->with('message', 'Upload Failed: ' . $fileUrl);
}
}
return redirect()->to('list')->with('message', 'Invalid file');
}
public function list()
{
$s3Service = new S3Service();
$objects = $s3Service->listFiles();
$data['objects'] = $objects;
return view('file_list', $data);
}
public function download($fileName)
{
$s3Service = new S3Service();
$fileUrl = $s3Service->getFileUrl($fileName);
return redirect()->to($fileUrl);
}
public function delete($fileName)
{
$s3Service = new S3Service();
$result = $s3Service->deleteFile($fileName);
if ($result === true) {
return $this->response->setJSON(['message' => 'File deleted successfully']);
}
return $this->response->setStatusCode(400)->setJSON(['error' => $result]);
}
}
?>
Improvements Added: Basic error checking in upload() to handle failures. You can add file validation (e.g., size/type) like this in upload() before processing:
$validationRule = [
'file' => 'uploaded[file]|max_size[file,10240]|ext_in[file,jpg,png,pdf]',
];
if (!$this->validate($validationRule)) {
return redirect()->to('list')->with('message', $this->validator->getErrors()['file']);
}
9 Create Index View File
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Upload to S3</title>
<!-- Add Bootstrap or CSS for better UX -->
</head>
<body>
<h2>Upload File to AWS S3</h2>
<?php if (session()->getFlashdata('message')): ?>
<p style="color:green;"><?= session()->getFlashdata('message') ?></p>
<?php endif; ?>
<form method="post" action="<?= base_url('upload') ?>" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</form>
<p><a href="<?= base_url('list') ?>">View Uploaded Files</a></p>
</body>
</html>
10 Create List View File
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>S3 File List</title>
<!-- Add Bootstrap or CSS for better UX -->
</head>
<body>
<h2>Files in AWS S3 Bucket</h2>
<?php if (session()->getFlashdata('message')): ?>
<p style="color:green;"><?= session()->getFlashdata('message') ?></p>
<?php endif; ?>
<table border="1" cellpadding="5" cellspacing="0">
<thead>
<tr>
<th>Filename</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php if (!empty($objects)): ?>
<?php foreach ($objects as $object): ?>
<?php if (isset($object['Key'])): ?>
<tr>
<td><?= esc($object['Key']) ?></td>
<td>
<a href="<?= base_url('download/' . rawurlencode($object['Key'])) ?>">Download</a>
<form action="<?= base_url('delete/' . rawurlencode($object['Key'])) ?>" method="post" style="display:inline;">
<?= csrf_field() ?>
<button type="submit" onclick="return confirm('Are you sure you want to delete this file?');">Delete</button>
</form>
</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="2">No files found.</td></tr>
<?php endif; ?>
</tbody>
</table>
<p><a href="<?= base_url() ?>">Upload Another File</a></p>
</body>
</html>
11 Define a Route
app/Config/Routes.php
use CodeIgniter\Router\RouteCollection;
$routes->get('/', 'Home::index');
$routes->get('/', 'S3Controller::index');
$routes->get('list', 'S3Controller::list');
$routes->post('upload', 'S3Controller::upload');
$routes->get('download/(:any)', 'S3Controller::download/$1');
$routes->delete('delete/(:any)', 'S3Controller::delete/$1');
12 Folder Structure
13 Run Web Server to Test the App
php spark serve
- Visit http://localhost:8080/ for the upload form.
- After upload, redirect to /list to see files.
- Download redirects to the S3 URL (for private files, this generates a temporary signed URL).
- Delete uses a POST form for security.
Best Practices & Troubleshooting
- Security: Use presigned URLs for private downloads (modify getFileUrl() to use createPresignedRequest() with expiration). Block public access on your bucket.
- Large Files: For files >100MB, implement multipart uploads in S3Service.
- Costs: Monitor S3 storage and data transfer fees.
- Common Issues:
- "Signature mismatch": Verify AWS region and credentials.
- "Permission denied": Check IAM policy and bucket permissions.
- "No such key": Ensure file keys match exactly.
- Testing: Upload small files first. Use AWS Console to verify bucket contents.
14 Conclusion
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
You need PHP 8.2 or higher, Composer installed, and an AWS account.
Run `composer create-project codeigniter4/appstarter ci-4-amazon-s3-app`, then `cd ci-4-amazon-s3-app`.
Copy `env` to `.env`, then set `CI_ENVIRONMENT = development` in the `.env` file.
Log into AWS Console, create an S3 bucket. Then in IAM, create a user, attach `AmazonS3FullAccess` policy, and generate Access Key ID and Secret Access Key.
Run `composer require aws/aws-sdk-php`.
Add these to your `.env` file: `AWS_ACCESS_KEY_ID=your-key`, `AWS_SECRET_ACCESS_KEY=your-secret`, `AWS_ENDPOINT=your-endpoint` (optional), `AWS_REGION=your-region`, `AWS_BUCKET=your-bucket-name`.
Create `app/Services/S3Service.php` with an S3Client instance using credentials from env, and methods for uploadFile, getFileUrl, listFiles, and deleteFile.
Run `php spark make:controller UploadController`, then implement index (for upload form), upload (handle file and use S3Service), list (list files), download (redirect to object URL), and delete methods.
Get the file with `$this->request->getFile('file')`, validate it, then use S3Service to upload with `uploadFile($filePath, $fileName)`.
In the list method, use `$s3Service->listFiles()` to get objects via ListObjects, then pass to the view and display file names with download/delete links.
Use `$s3Service->getObjectUrl($bucket, $key)` to generate the URL and redirect to it (files are private by default with ACL 'private').
Verify AWS credentials, region, bucket name in `.env`. Ensure the IAM user has proper S3 permissions (e.g., s3:PutObject, s3:GetObject, s3:ListBucket).
Change 'ACL' => 'private' to 'ACL' => 'public-read' in the putObject call in S3Service.
Start the server with `php spark serve`, visit the root route (e.g., `/`) for upload form, and `/list` to view files.
