How Does Infinite Scroll Work in CodeIgniter 4 Using AJAX and jQuery? (Step-by-Step Overview)

             

Implementing infinite scroll in CodeIgniter 4 follows a clean client-server pattern. Here's how it works end-to-end:

  1. Initial Page Load: The main controller method (e.g., UserController::index()) fetches the first page of records using the model's paginate() or manual limit()/offset(). It passes data to the view, which renders cards/items in a container div (e.g., #users-container).
  2. Scroll Detection: jQuery's $(window).scroll() event monitors position. When the user nears the bottom (e.g., scrollTop + window height > document height - 300), it triggers a function if not already loading.
  3. AJAX Request: An AJAX GET/POST call sends the next page number to a dedicated endpoint (e.g., /users/load-more?page=2). Flags like isLoading prevent duplicate requests during slow networks.
  4. Server-Side Handling: The controller receives the page, calculates offset ((page-1) * perPage), queries the database for the next batch, and returns a partial view (e.g., load_users.php) containing only the new HTML items — no full layout.
  5. Dynamic Append & State Update: On success, jQuery appends the returned HTML to the container, increments the page counter, hides the loader, and checks if more data exists (empty response = stop loading, show "No more" message).
  6. Enhancements: Add a spinner for feedback, handle errors gracefully, debounce the scroll event for performance, and optionally use Intersection Observer for a jQuery-free modern alternative.

This method ensures smooth, efficient data loading without page refreshes. With proper CSRF handling (if enabled) and accessibility considerations (e.g., aria-live regions for new content), it's robust for production. The entire flow keeps your CodeIgniter 4 app fast, scalable, and user-friendly for large datasets.



CodeIgniter 4: Load More Data on Page Scroll with AJAX and jQuery (Infinite Scroll)

Table Of Content

1 Prerequisites

  • PHP ≥ 8.1
  • Composer
  • MySQL database
  • Basic knowledge of CodeIgniter 4 (Models, Controllers, Views, Migrations)

2 Introduction

Below is the complete, working code for implementing infinite scroll (load more data on page scroll) in a fresh CodeIgniter 4 project. This includes all necessary files: configuration, migration, model, seeder, controller, routes, and views. I've structured it by file path for easy copying into your project.

3 Create / Install a Codeigniter 4 Project

3.1 Install Codeigniter 4 Project

Make sure you have Composer installed. Run the following command to create a new CodeIgniter project:

composer create-project codeigniter4/appstarter ci-4-ajax-load-more-app

Then, navigate to your project directory:

cd ci-4-ajax-load-more-app

3.2 Configure Environment (.env)

After installing CodeIgniter 4, rename the env file to .env for environment variables.

sudo cp env .env

Switch to development mode by updating the .env file:

# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
database.default.hostname = localhost
database.default.database = ci4_infinite_scroll_db  # Create this DB in MySQL
database.default.username = root
database.default.password = 
database.default.DBDriver = MySQLi


Now application is in development mode.

4 Create Migration and Model

To manage user data, create a migration for the users table and a model.

Create a migration file for the users table:



php spark make:migration CreateUsersTable

Define the table structure in the migration file.

<?php
namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateUsersTable extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'BIGINT',
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'name' => [
                'type' => 'VARCHAR',
                'constraint' => '255',
            ],
            'email' => [
                'type' => 'VARCHAR',
                'constraint' => '255',
            ],
            'created_at' => [
                'type' => 'TIMESTAMP',
                'null' => true,
            ],
            'updated_at' => [
                'type' => 'TIMESTAMP',
                'null' => true,
            ],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('users');
    }

    public function down()
    {
        $this->forge->dropTable('users');
    }
}

Run the migration:

php spark migrate


php spark make:model UserModel

Edit UserModel.php to define the users table structure. Then, create the migration for the table:

<?php
namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table            = 'users';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'array';
    protected $useSoftDeletes   = false;
    protected $protectFields    = true;
    protected $allowedFields    = ['name', 'email', 'created_at', 'updated_at'];

    // Dates
    protected $useTimestamps = false;
    protected $dateFormat    = 'datetime';
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';
    protected $deletedField  = 'deleted_at';

    // Validation
    protected $validationRules      = [];
    protected $validationMessages   = [];
    protected $skipValidation       = false;
    protected $cleanValidationRules = true;

    // Callbacks
    protected $allowCallbacks = true;
    protected $beforeInsert   = [];
    protected $afterInsert    = [];
    protected $beforeUpdate   = [];
    protected $afterUpdate    = [];
    protected $beforeFind     = [];
    protected $afterFind      = [];
    protected $beforeDelete   = [];
    protected $afterDelete    = [];
}

5 Create Database Seeder

To populate the users table with test data, use the seeder class.
Run this command:

php spark make:seeder UserSeeder

Edit UserSeeder.php to generate 100 fake users using the Faker library, then run the seeder:

<?php
namespace App\Database\Seeds;

use CodeIgniter\Database\Seeder;
use CodeIgniter\I18n\Time;
use Faker\Factory;

class UserSeeder extends Seeder
{
    public function run()
    {
        $faker = Factory::create();
        $model = model('App\Models\UserModel');

        for ($i = 0; $i < 100; $i++) {
            $model->insert([
                'name'       => $faker->name,
                'email'      => $faker->unique()->safeEmail,
                'created_at' => $faker->dateTime->format('Y-m-d H:i:s'),
                'updated_at' => Time::now()->toDateTimeString(),
            ]);
        }
    }
}

Run this below command to insert the data.

php spark db:seed UserSeeder

6 Create New Controller - UserController

Create a controller to handle data requests.

php spark make:controller UserController

In UserController.php, define methods to load data on page scroll. The loadMore method will handle Ajax requests, loading more data based on the page offset.

<?php
namespace App\Controllers;

use App\Models\UserModel;
use CodeIgniter\API\ResponseTrait;

class UserController extends BaseController
{
    use ResponseTrait;

    protected $userModel;

    public function __construct()
    {
        $this->userModel = new UserModel();
    }

    public function index()
    {
        $perPage = 8; // Initial load
        $data['users'] = $this->userModel->orderBy('id', 'DESC')->findAll($perPage, 0);
        return view('users/index', $data);
    }

    public function loadMore()
    {
        $page = $this->request->getGet('page') ?? 2;
        $perPage = 8;
        $offset = ($page - 1) * $perPage;

        $users = $this->userModel->orderBy('id', 'DESC')->findAll($perPage, $offset);

        if (empty($users)) {
            return $this->respond(''); // No more data
        }

        return view('users/load_more', ['users' => $users]);
    }
}
?>

7 Create a View

Create a view file index.php in the app/Views directory to display the data:

   <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CodeIgniter 4 Infinite Scroll: Load More Data on Scroll with AJAX & jQuery</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
</head>
<body class="bg-light">

<div class="container my-5">
    <h1 class="mb-4">CodeIgniter 4: Load More Data on Page Scroll with AJAX and jQuery (Infinite Scroll)</h1>
    
    <div id="users-container">
        <?php foreach ($users as $user): ?>
            <div class="card mb-3 shadow-sm">
                <div class="card-body">
                    <h5 class="card-title"><?= esc($user['name']) ?></h5>
                    <p class="card-text text-muted"><?= esc($user['email']) ?></p>
                </div>
            </div>
        <?php endforeach; ?>
    </div>
    
    <div id="loading" class="text-center my-4" style="display: none;">
        <div class="spinner-border text-primary" role="status">
            <span class="visually-hidden">Loading...</span>
        </div>
        <p>Loading more users...</p>
    </div>
    
    <div id="no-more" class="text-center my-4 text-muted" style="display: none;">
        No more users to load.
    </div>
</div>

<script>
    let page = 2;
    let isLoading = false;
    let hasMore = true;

    $(window).on('scroll', function() {
        if ($(window).scrollTop() + $(window).height() > $(document).height() - 300 && !isLoading && hasMore) {
            loadMoreData();
        }
    });

    function loadMoreData() {
        isLoading = true;
        $('#loading').show();

        $.ajax({
            url: '<?= base_url('users/load-more') ?>',
            type: 'GET',
            data: { page: page },
            success: function(data) {
                if (data.trim() === '') {
                    hasMore = false;
                    $('#no-more').show();
                } else {
                    $('#users-container').append(data);
                    page++;
                }
                $('#loading').hide();
                isLoading = false;
            },
            error: function(xhr, status, error) {
                console.error('AJAX Error:', error);
                $('#loading').hide();
                isLoading = false;
            }
        });
    }
</script>

</body>
</html>

Create a view file load_more.php in the app/Views directory to display the load more data:

<?php foreach ($users as $user): ?>
    <div class="card mb-3 shadow-sm">
        <div class="card-body">
            <h5 class="card-title"><?= esc($user['name']) ?></h5>
            <p class="card-text text-muted"><?= esc($user['email']) ?></p>
        </div>
    </div>
<?php endforeach; ?>

8 Define a Route

In app/Config/Routes.php, define the routes for the controller methods:

use CodeIgniter\Router\RouteCollection;

/**
 * @var RouteCollection $routes
 */
$routes->get('/', 'Home::index');
$routes->get('users', 'UserController::index');
$routes->get('users/load-more', 'UserController::loadMore');

9 Folder Structure

The structure for this CodeIgniter project:

10 Run Web Server to Test the App

Use the following command to start the web server and test the application:

php spark serve

Visit http://localhost:8080/index.php to see the app in action.

11 Conclusion

Implementing infinite scroll (load more data on page scroll using AJAX and jQuery) in CodeIgniter 4 is an excellent way to modernize your web applications and deliver a smoother, more engaging user experience. By replacing traditional pagination with seamless, automatic content loading, you eliminate page reloads, reduce user friction, and encourage visitors to explore more of your content effortlessly — whether it's a product catalog, blog archive, news feed, or user directory.

In this complete tutorial, we covered every essential step: setting up a fresh CodeIgniter 4 project, creating the database table and seeding sample data, building an efficient UserModel, handling pagination logic in the controller (both initial load and AJAX endpoint), defining clean routes, crafting responsive views with Bootstrap cards, and writing robust jQuery code to detect scroll position, fetch additional records asynchronously, append them dynamically, and manage loading states with a spinner and “no more data” message.

The result is a lightweight, performant solution that works beautifully on both desktop and mobile devices without relying on heavy frontend frameworks. You now have a production-ready infinite scroll feature that can be easily adapted to any model or dataset in your CodeIgniter 4 application. For even better performance, consider adding scroll event debouncing, switching to the native Intersection Observer API (to remove jQuery dependency), implementing lazy loading for images inside the cards, or adding browser history support with pushState for better shareability and SEO.

As web users increasingly expect fluid, app-like experiences in 2026, mastering techniques like AJAX-powered infinite scroll gives your projects a clear competitive edge. Test the implementation thoroughly, monitor page speed and bounce rates, and watch user engagement improve. Happy coding — your CodeIgniter 4 apps just got a lot more addictive!

Quick recap of benefits you now enjoy:

  • Improved user retention and time-on-page
  • Faster initial page loads with incremental data fetching
  • Mobile-friendly scrolling behavior
  • Easy integration into existing CI4 projects
  • Foundation for advanced features like pull-to-refresh or virtual scrolling

Thanks for following along — feel free to share your results, ask questions in the comments, or extend this pattern to real-time feeds with WebSockets. Keep building awesome things!

Revathi M - PHP and CodeIgniter Developer

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.1+, Composer, MySQL, and a fresh CodeIgniter 4 installation. Basic knowledge of migrations, models, controllers, views, and jQuery is required.

Create a migration for the 'users' table with fields: id (BIGINT unsigned auto_increment), name (VARCHAR 255), email (LONGTEXT), created_at and updated_at (TIMESTAMP nullable). Run php spark migrate and seed 100 fake records using UserSeeder with Faker.

In UserController, onScrollLoadMore() gets the page parameter, calculates offset ($start = 4 * (page - 1)), fetches data with limit 4 and offset, and returns the partial 'load_users' view.

jQuery detects when scroll is near bottom ($(window).scrollTop() + $(window).height() >= $(document).height() - 555). It increments page, prevents multiple loads with a flag, and AJAX GETs 'onScrollLoadMore?page=' + page to append HTML.

In index.php, show initial users in Bootstrap cards inside #loadMoreBlock. In load_users.php (partial), loop through $users and output each name in a card.

Common issues: jQuery CDN not loaded, incorrect base_url in JS, route mismatch, or database empty. Check console for errors, ensure isLoading flag works, and verify offset calculation.

If AJAX returns empty data, set triggerScrollLoader to false to stop further scroll events and hide the loader.

The JavaScript shows/hides a #loader element, but the tutorial HTML does not include it—add a

Loading...
manually for it to work.

Yes, it uses Bootstrap 5 for card styling (card mb-3). Include Bootstrap CDN in the main view.

Add a visible loading spinner, error handling in AJAX fail(), caching, or use Intersection Observer API instead of jQuery scroll for better performance.