Panduan CMS, logo guide, cara membuat template kustom, dan referensi API.
Nalar CMS membantu kamu mengelola situs berita dari satu dasbor — mulai dari menulis artikel sampai mengatur tampilan situs.
Editor mendukung format teks tebal, miring, tautan, gambar, dan daftar tanpa plugin tambahan. Setiap artikel punya slug otomatis dari judul, ringkasan untuk pratinjau share, dan kolom SEO terpisah.
Status artikel bisa Draft, Terbit sekarang, atau Jadwalkan untuk tayang otomatis di waktu tertentu.
Aktifkan keduanya dari panel kanan saat menulis atau mengedit artikel.
Kategori bisa disusun berjenjang (induk & anak) untuk navigasi situs, sementara tag membantu mengelompokkan artikel lintas kategori. Kelola keduanya dari halaman Kategori & Tag.
Mode Mudah mengatur warna, font, dan lebar konten tanpa kode. Mode Lanjutan memberi akses CSS per komponen untuk penyesuaian lebih dalam — CSS divalidasi otomatis sebelum disimpan.
Ingin membuat template baru dari nol? Lihat tab Membuat Template.
Butuh referensi identitas visual Nalar (eksplorasi logo, proporsi, dan variasi)? Unduh panduan PDF di bawah untuk dipakai saat desain materi, deck, atau aset brand.
Undang rekan sebagai Penulis (kelola artikel sendiri) atau Editor (kelola semua konten) dari Pengaturan → Pengguna.
Panduan ini menuntun kamu membuat template PHP baru yang bisa dipilih di Template & Desain dan dipakai situs publik. Contoh di bawah memakai kode sport — ganti jadi nama template kamu sendiri (huruf kecil, tanpa spasi).
templates/sport/ dengan home.php + article.php, baris di tabel templates, kartu di galeri CMS, lalu bisa di-Activate.
templates/, public/assets/js/).Kalau baru belajar, salin dulu templates/classic/ lalu ubah markupnya — lebih cepat daripada mulai kosong.
Kode = nama folder = nilai templates.code di database. Harus unik.
sport, kuliner, techblognews-24classic, modern, minimal, otosport, nama tampilan = Sport Daily.
Dari root proyek, buat folder baru:
mkdir -p templates/sport
Struktur target setelah langkah 4–5:
templates/ _layout_start.php ← jangan diubah (shared) _layout_end.php ← jangan diubah (shared) sport/ home.php ← wajib article.php ← wajib styles.inc.php ← opsional
sport tapi DB-nya sports, engine akan fallback ke Classic.
home.php (beranda)Buat file templates/sport/home.php. Salin seluruh isi di bawah ini:
<?php // Meta default (boleh di-override dari luar) $metaTitle = $metaTitle ?? ($site['meta_title'] ?: $site['name']); $metaDesc = $metaDesc ?? ($site['meta_description'] ?? ''); // Selalu mulai & akhiri dengan layout shared include TEMPLATES_PATH . '/_layout_start.php'; ?> <header class="site-header"> <div class="container" style="padding:0"> <h1> <a href="/" style="color:inherit;text-decoration:none"> <?= e($site['name']) ?> </a> </h1> <?php if (!empty($tagline)): ?> <p><?= e($tagline) ?></p> <?php endif; ?> </div> </header> <main class="container"> <?php if (empty($articles)): ?> <p>Belum ada artikel.</p> <?php else: ?> <?php foreach ($articles as $a): ?> <article class="article-card"> <div class="article-meta"> <?= e($a['category_name'] ?? 'Umum') ?> · <?= e(article_date_label($a)) ?> </div> <h2> <a href="/artikel/<?= e($a['slug']) ?>"> <?= e($a['title']) ?> </a> </h2> <?php if (!empty($a['excerpt'])): ?> <p class="excerpt"><?= e($a['excerpt']) ?></p> <?php endif; ?> </article> <?php endforeach; ?> <?php endif; ?> </main> <?php include TEMPLATES_PATH . '/_layout_end.php'; ?>
include _layout_start.php di awal dan _layout_end.php di akhir.e()./artikel/{slug}.$articles, $site, $opts, $tagline sudah diisi engine — tidak perlu query DB sendiri.article.php (halaman detail)Buat file templates/sport/article.php:
<?php include TEMPLATES_PATH . '/_layout_start.php'; ?> <header class="site-header"> <div class="container" style="padding:0"> <h1> <a href="/" style="color:inherit;text-decoration:none"> <?= e($site['name']) ?> </a> </h1> </div> </header> <main class="container"> <article> <div class="article-meta"> <?= e($article['category_name'] ?? '') ?> · <?= e($article['author_name'] ?? '') ?> · <?= e(article_date_label($article)) ?> </div> <h1 style="font-size:32px;margin:8px 0 16px"> <?= e($article['title']) ?> </h1> <?php if (!empty($article['excerpt'])): ?> <p style="font-size:18px;opacity:.75;margin-bottom:24px"> <?= e($article['excerpt']) ?> </p> <?php endif; ?> <div class="article-body"> <?= nl2br(e($article['content'] ?? '')) ?> </div> </article> <?php if (!empty($related)): ?> <section style="margin-top:40px;border-top:1px solid rgba(0,0,0,.08);padding-top:24px"> <h3 style="margin:0 0 16px">Artikel Lainnya</h3> <?php foreach ($related as $r): ?> <div style="padding:8px 0"> <a href="/artikel/<?= e($r['slug']) ?>"><?= e($r['title']) ?></a> </div> <?php endforeach; ?> </section> <?php endif; ?> </main> <?php include TEMPLATES_PATH . '/_layout_end.php'; ?>
Di halaman ini engine mengirim $article (satu berita) dan $related (artikel terkait).
Warna & font dasar sudah diurus _layout_start.php lewat CSS variables dari Mode Mudah CMS. Kalau butuh gaya ekstra (hero, grid, dsb.), buat file:
.sport-hero {
background: var(--color-primary);
color: #fff;
padding: 28px 20px;
border-radius: var(--radius);
margin-bottom: 24px;
}
.sport-hero h2 { margin: 0; font-family: var(--font-heading); }Supaya CSS ini ikut ter-load, kamu perlu menambahkannya di TemplateRenderer (lihat langkah 8). Untuk template minimal, langkah 5 & 8 bisa dilewati dulu — layout shared sudah cukup.
--color-primary --color-accent --color-bg --color-header --color-text --font-heading --font-body --scale --lh --content-w --radius --gap
Nilai diisi dari opsi CMS: primary, accent, bg, text, header, fontHeading, fontBody, scale, lh, width, radius, gap, sidebar, shadow, ticker, thumbs.
Tanpa baris di tabel templates, tombol Activate di CMS akan gagal. Jalankan SQL berikut (ganti nilai sesuai template kamu):
INSERT INTO templates (code, name, category, description, author, version, base_html, base_css, default_opts, is_active, created_at, updated_at) VALUES ( 'sport', 'Sport Daily', 'berita', 'Portal olahraga sederhana — daftar berita + halaman detail.', 'Tim Kamu', '1.0.0', '<!-- sport -->', '/* sport */', '{"primary":"#0b3d91","accent":"#f59e0b","bg":"#ffffff","text":"#111827","header":"#e8eef8","fontHeading":"Inter","fontBody":"Inter","scale":1,"lh":1.65,"width":1180,"radius":8,"gap":1,"sidebar":"right","shadow":false,"ticker":false,"thumbs":true}', 1, NOW(), NOW() );
Cek hasilnya:
SELECT id, code, name, is_active FROM templates WHERE code = 'sport';
scripts/seed.php lalu jalankan php scripts/seed.php (hati-hati: seed juga menyentuh data lain).
Galeri di halaman Template & Desain membaca array TEMPLATES di file JS — bukan langsung dari DB. Buka:
public/assets/js/template-engine.js
Tambahkan objek baru di dalam array TEMPLATES (setelah entry oto misalnya):
{
id: 'sport',
dbCode: 'sport',
name: 'Sport Daily',
category: 'berita',
catLabel: 'Olahraga',
desc: 'Portal olahraga sederhana dengan daftar berita dan halaman detail.',
author: 'Tim Kamu',
version: '1.0.0',
php: true,
opts: {
primary: '#0b3d91', accent: '#f59e0b', bg: '#ffffff',
text: '#111827', header: '#e8eef8',
fontHeading: 'Inter', fontBody: 'Inter',
scale: 1, lh: 1.65, width: 1180, radius: 8, gap: 1,
sidebar: 'right', shadow: false, ticker: false, thumbs: true
},
css: ''
},id — ID di sisi JS (biasanya sama dengan kode).dbCode — harus sama dengan templates.code di DB. Saat Activate, nilai ini yang dikirim ke server.php: true — pratinjau memakai iframe PHP asli, bukan mock HTML.opts — default Mode Mudah; samakan dengan default_opts di DB.Lalu buka juga resources/views/cms/template-customize.php dan tambahkan mapping di ENGINE_IDS:
var ENGINE_IDS = {
classic: 'classic',
modern: 'magazine',
minimal: 'minimal',
oto: 'oto',
sport: 'sport' // ← tambahkan
};Langkah ini hanya perlu jika beranda butuh data di luar daftar artikel biasa — misalnya slider highlight, pilihan editor, atau daftar kategori.
Buka app/Services/TemplateRenderer.php, method homeData(). Contoh pola (mirip Oto):
if ($code === 'sport') { $data['latest'] = $articles; $data['heroSlides'] = Article::highlightsForSite($siteId, 3); $data['editorPicks'] = Article::editorPicksForSite($siteId, 4); $data['categories'] = Category::forSite($siteId); $styles = TEMPLATES_PATH . '/sport/styles.inc.php'; if (is_file($styles)) { ob_start(); include $styles; $data['customCss'] = $customCss . "\n" . (string) ob_get_clean(); } }
Lalu di home.php kamu bisa pakai $heroSlides, $editorPicks, dst.
is_highlight) → Article::highlightsForSite() → cocok untuk slider/hero.is_editor_pick) → Article::editorPicksForSite() → cocok untuk section khusus.Aktifkan flag-nya dari form artikel di CMS (panel Publikasikan).
Kalau ingin layout sendiri untuk kategori / pencarian / kontak, tambahkan file di folder template:
category.php → URL /kategori/{slug}search.php → URL /cari?q=...contact.php → URL /kontakLihat contoh lengkap di templates/oto/.
home.php kamu (bukan Classic).article.php.templates/sport/ ada, berisi home.php + article.phpSELECT code FROM templates WHERE code='sport' AND is_active=1 mengembalikan 1 baristemplate-engine.js dengan php: true dan dbCode: 'sport'ENGINE_IDS di template-customize memuat sporttemplate-engine.js (buka Console browser).templates dan pastikan dbCode JS = code DB.home.php belum ada / path salah.e() dan tidak ada typo variabel.var(--color-primary) dll., jangan hardcode hex di CSS template.$site — name, tagline, meta_title, meta_description, tracking, …$opts — opsi desain Mode Mudah$customCss — CSS kustom situs (+ CSS template jika di-inject)$metaTitle, $metaDesc$isPreview — true saat pratinjau CMS (jangan muat GA/iklan)$tagline — sudah di-extract di layout start dari $site['tagline']$articles — daftar artikel terbit$latest, $heroSlides, $editorPicks, $popular, $categories — jika kamu inject di langkah 8$article — satu artikel (title, slug, content, excerpt, category_name, author_name, …)$related — artikel terkaite($str) — escape HTML (wajib)article_image_url($article) — URL gambar utamaarticle_date_label($article) — tanggal lokal siap tampilTEMPLATES_PATH — path absolut ke folder templates// — beranda → home.php/artikel/{slug} — detail → article.php/kategori/{slug} — kategori → category.php/cari?q=... — pencarian → search.php/kontak — kontak → contact.phpe().<script> di custom HTML situs; tracking lewat Pengaturan situs.$isPreview — jangan muat GA/GTM/iklan saat pratinjau.@import di CSS kustom (diblokir validator).dbCode di JS — harus sama persis.templates/oto/ sebagai referensi lengkap.Semua permintaan API memerlukan header Authorization. Ambil kunci API kamu dari Pengaturan → API & Integrasi.
# contoh permintaan curl http://nalar.link/api/v1/articles \ -H "Authorization: Bearer SASSLY_API_KEY"
Mengambil daftar artikel. Mendukung parameter status, category, dan page.
Mengambil satu artikel berdasarkan ID.
// contoh respons { "id": "1", "title": "Harga Emas Antam Naik...", "status": "published", "category": "ekonomi", "is_highlight": true, "is_editor_pick": false }
Mengambil daftar kategori beserta jumlah artikel di dalamnya.
401 kunci API tidak valid atau kedaluwarsa. 429 permintaan melebihi limit rate. 500 galat di sisi server.