Conventions
Naming
- Table names:
snake_case, plural (e.g.posts,widget_instances,author_profiles) - Model class names: Singular PascalCase (e.g.
PostModel,WidgetInstanceModel) - Primary key: always
id—INT UNSIGNED AUTO_INCREMENT - Foreign keys:
{related_table_singular}_id(e.g.post_id,user_id,widget_id) - Pivot tables:
{table1}_to_{table2}in alphabetical order where possible (e.g.posts_to_categories,tags_to_posts) - Plugin tables: prefixed with a short plugin code + underscore (e.g.
pvd_articles,ds_products,vt_events)
Timestamps
All core tables have:
created_at DATETIME NULL,
updated_at DATETIME NULL
CI4's Model timestamps feature manages these automatically when $useTimestamps = true.
Soft Deletes
Content tables (posts, pages, comments) use soft delete via:
deleted_at DATETIME NULL
CI4's SoftDeleteTrait (or the $useSoftDeletes = true Model flag) filters these rows from standard queries.
Status Enums
Content status (posts, pages)
| Value | Meaning |
|---|---|
draft | Not visible publicly |
published | Live and visible |
scheduled | Publish at scheduled_at datetime |
Comment status
| Value | Meaning |
|---|---|
approved | Visible on post page |
pending | Awaiting moderation |
spam | Flagged as spam, hidden |
trash | Soft-deleted |
Extension status (themes, plugins, widgets)
| Value | Meaning |
|---|---|
active | Loaded and in use |
inactive | Installed but disabled |
unavailable | Directory missing from filesystem |
Boolean Columns
Stored as TINYINT(1). Use 1 for true, 0 for false. CI4 Model casting should be configured for boolean columns:
protected $casts = [
'is_premium' => 'boolean',
'share_on_publish' => 'boolean',
'is_active' => 'boolean',
];
Plugin Table Prefixes
Plugin developers must prefix their tables to avoid conflicts with core tables and other plugins. Use 2–4 character prefix derived from the plugin name:
| Plugin | Prefix | Example table |
|---|---|---|
| PvDocs | pvd_ | pvd_articles, pvd_versions |
| DStore | ds_ | ds_products, ds_orders |
| VenueTix | vt_ | vt_events, vt_tickets |
CI4 Query Builder Conventions
Pubvana models extend CodeIgniterModel. Direct DB access in plugins should go through a Model class, not raw db() calls, to maintain soft delete consistency and timestamp management:
// Correct — goes through Model with soft delete filter
$posts = model('PostModel')->where('status', 'published')->findAll();
// Use with care — bypasses soft deletes
$db = db_connect();
$posts = $db->table('posts')->where('status', 'published')->get()->getResultObject();