Views
Plugin views come in two distinct types with different locations, rendering mechanisms, and syntax rules.
Two View Types
| Type | Directory | Syntax | Rendered via | Layout |
|---|---|---|---|---|
| Admin views | Views/admin/ | PHP | view() | SB Admin 2 |
| Public views | Views/store/ | .tpl | themeService->view() | Active theme |
Admin Views
Rendering
return view('Plugins\MyPlugin\Views\admin\index', $data);
Example Admin View
<div class="container-fluid">
<h1 class="h3 mb-4"><?= lang('MyPlugin.items_heading') ?></h1>
<?php if (session('success')): ?>
<div class="alert alert-success"><?= esc(session('success')) ?></div>
<?php endif; ?>
<table class="table table-bordered">
<thead><tr><th>Title</th><th>Status</th><th>Actions</th></tr></thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?= esc($item->title) ?></td>
<td><?= esc($item->status) ?></td>
<td><a href="/myplugin/admin/items/<?= $item->id ?>/edit"
class="btn btn-sm btn-primary">Edit</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
All admin forms must include <?= csrf_field() ?>.
Public Views
Rendering
return $this->themeService->view('PluginsMyPluginViewsstoreindex', $data);
Example Public View (.tpl)
{% extends "layout.tpl" %}
{% block content %}
<div class="{{ cls.cls_container }}">
<h1>{{ page_title }}</h1>
{% for item in items %}
<div class="{{ cls.cls_card }}">
<h2>{{ item.title }}</h2>
<a href="/myplugin/{{ item.slug }}" class="{{ cls.cls_btn_primary }}">
{! lang "MyPlugin.read_more" !}
</a>
</div>
{% endfor %}
{! pager !}
</div>
{% endblock %}
What NOT to Do
- Do not use
<?phpin.tplfiles - Do not call CI4's
view()for public pages — usethemeService->view() - Do not hardcode CSS class names — use
{{ cls.* }}keys - Do not include Bootstrap CSS in your plugin — the active theme provides the CSS framework