What is a Dynamic XML Sitemap?
A dynamic XML sitemap is an automatically generated XML file (typically accessed at /sitemap.xml) that updates in real-time whenever your site content changes—new posts are published, existing pages are edited, or URLs are added/removed. Unlike static sitemaps that require manual updates and file regeneration, a dynamic version in CodeIgniter 4 queries your database on each request, ensuring search engine crawlers always see the most current structure. It includes critical metadata such as lastmod (last modification date), changefreq, and priority, helping Google prioritize crawling and improve indexing speed and accuracy.

Table Of Content
1 Prerequisites
- PHP ≥ 8.1 (CodeIgniter 4 requirement)
- Composer
- MySQL database
2 Introduction
In today's competitive digital landscape, ensuring search engines like Google can efficiently discover and index your website's content is essential for strong SEO performance. A dynamic XML sitemap in CodeIgniter 4 solves this by automatically generating an up-to-date list of your URLs, including posts, pages, categories, or products pulled directly from your database.
3 Install CodeIgniter 4 Project
3.1 Install Codeigniter 4 Project
Use the following command to install new Codeigniter Project.
composer create-project codeigniter4/appstarter ci4-sitemap
Then, navigate to your project directory:
cd ci4-sitemap
3.2 Configure Environment and MySql Database
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
database.default.hostname = localhost
database.default.database = ci4_sitemap
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi
Create the database ci4_sitemap.
4 Create Migration and Model
php spark make:model Post --suffix
Edit app/Models/PostModel.php to configure the model:
<?php
namespace App\Models;
use CodeIgniter\Model;
class PostModel extends Model
{
protected $table = 'posts';
protected $primaryKey = 'id';
protected $allowedFields = ['title', 'slug', 'content', 'created_at', 'updated_at'];
protected $useTimestamps = true;
}
Create a migration file for the posts table:
php spark make:migration CreatePostsTable
Edit the migration file to define the table structure:
<?php
forge->addField([
'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
'title' => ['type' => 'VARCHAR', 'constraint' => 255],
'slug' => ['type' => 'VARCHAR', 'constraint' => 255, 'unique' => true],
'content' => ['type' => 'TEXT'],
'created_at' => ['type' => 'DATETIME', 'null' => true],
'updated_at' => ['type' => 'DATETIME', 'null' => true],
]);
$this->forge->addPrimaryKey('id');
$this->forge->createTable('posts');
}
public function down()
{
$this->forge->dropTable('posts');
}
}
Run the migration:
php spark migrate
5 Create Database Seeder
php spark make:seeder PostSeeder
Inside the PostSeeder.php file, use the Faker library to generate fake data:
<?php
sentence(6);
$model->insert([
'title' => $title,
'slug' => url_title($title, '-', true),
'content' => $faker->paragraphs(3, true),
'created_at' => $faker->dateTimeThisYear(),
'updated_at' => $faker->dateTimeThisMonth(),
]);
}
}
}
Run this below command to insert the data.
php spark db:seed PostSeeder
6 Create Sitemap Controller
php spark make:controller Sitemap
In app/Controllers/Sitemap.php, define the index function:
<?php
namespace App\Controllers;
use App\Models\PostModel;
use CodeIgniter\HTTP\ResponseInterface;
class Sitemap extends Controller
{
public function index(): ResponseInterface
{
$this->response->setContentType('application/xml');
$postModel = new PostModel();
$posts = $postModel->findAll();
return view('sitemap', ['posts' => $posts]);
}
}
?>
7 Create a View
<?php
header('Content-Type: application/xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc><?= esc(base_url()) ?></loc>
<lastmod><?= date('c') ?></lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<?php foreach ($posts as $post): ?>
<url>
<loc><?= esc(base_url('post/' . $post['slug'])) ?></loc>
<lastmod><?= date('c', strtotime($post['updated_at'])) ?></lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<?php endforeach; ?>
</urlset>
8 Define a Route
use CodeIgniter\Router\RouteCollection;
/**
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');
$routes->get('sitemap.xml', 'Sitemap::index');
9 Folder Structure
10 Run Web Server to Test the App
php spark serve
Visit: http://localhost:8080/sitemap.xml
You should see valid XML. Submit to Google Search Console.
Best Practices & Tips
- Use real updated_at for accurate lastmod.
- For large sites (>50k URLs), implement sitemap index files or caching (e.g., via CI Cache library).
- Add static pages manually in the view.
- Validate with tools like XML-Sitemaps.com validator.
- Ping search engines after major updates.
11 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
A dynamic XML sitemap helps search engines like Google, Bing, and Yahoo discover and crawl your website pages more efficiently, improving SEO and indexing of dynamic content.
Create a migration for a 'posts' table with fields like id, name, slug, description, created_at, and updated_at. Run the migration and use a seeder with Faker to insert test data.
In the Sitemap controller, use a PostModel to fetch all records with findAll(). Pass the data to a view that loops through posts and generates
In app/Config/Routes.php, add $routes->get('sitemap.xml', 'Sitemap::index');. This serves the XML output when accessing /sitemap.xml.
The view (e.g., app/Views/index.php) outputs XML with '; ?>,
In the view, use
Yes, manually add
Common issues: Incorrect route, view not returning XML headers, or database connection problems. Ensure the controller calls view('index', $data) directly (no layout) and test with php spark serve.
Store updated_at in the database and use
For production with many URLs, add caching in the controller (e.g., CodeIgniter's Cache library) to avoid database queries on every request.
